/* * 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.eclipse.jdt.groovy.core.util; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassCodeVisitorSupport; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.control.SourceUnit; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.internal.core.util.Util; /** * Helper methods - can be made more eclipse friendly or replaced if the groovy infrastructure provides the information. */ public abstract class GroovyUtils { public static char[] readSourceRange(SourceUnit unit, int offset, int length) { Reader reader = null; try { reader = unit.getSource().getReader(); reader.skip(offset); int n = length; final char[] code = new char[n]; while (n > 0) { n -= reader.read(code, length - n, n); } return code; } catch (Exception e) { Util.log(e); } finally { try { if (reader != null) reader.close(); } catch (IOException ignored) {} } return null; } // FIXASC don't use this any more? public static int[] getSourceLineSeparatorsIn(char[] code) { List<Integer> lineSeparatorsCollection = new ArrayList<Integer>(); for (int i = 0, max = code.length; i < max; i++) { if (code[i] == '\r') { if ((i + 1) < max && code[i + 1] == '\n') {// \r\n lineSeparatorsCollection.add(i + 1); // add the position of the \n i++; } else { lineSeparatorsCollection.add(i); // add the position of the \r } } else if (code[i] == '\n') { lineSeparatorsCollection.add(i); } } int[] lineSepPositions = new int[lineSeparatorsCollection.size()]; for (int i = 0; i < lineSeparatorsCollection.size(); i++) { lineSepPositions[i] = lineSeparatorsCollection.get(i); } return lineSepPositions; } /** * Finds the rightmost node given an annotation. This could be the node * itself, its source annotation (in the case of an annotation collector), * or the last member expression. */ public static ASTNode lastElement(AnnotationNode node) { ASTNode result = node; @SuppressWarnings("unchecked") Iterable<AnnotationNode> more = (Iterable<AnnotationNode>) node.getNodeMetaData("AnnotationCollector"); if (more != null) { for (AnnotationNode an : more) { result = an; } } if (((AnnotationNode) result).getMembers() != null) { for (Expression expr : ((AnnotationNode) result).getMembers().values()) { expr = ClassCodeVisitorSupport.getNonInlinedExpression(expr); if (expr.getEnd() > result.getEnd()) { result = expr; } } } return result; } /** * @return position of '@' (or best approximation) for specified annotation */ public static int startOffset(AnnotationNode node) { int start = -1; Long offsets = (Long) node.getNodeMetaData("source.offsets"); if (offsets != null) { start = (int) (offsets >> 32); } else if (node.getEnd() > 0) { start = node.getStart() - 1; } return start; } /** * @return position of ')' (or best approximation) for specified annotation */ public static int endOffset(AnnotationNode node) { int end = -1; Long offsets = (Long) node.getNodeMetaData("source.offsets"); if (offsets != null) { end = (int) (offsets & 0xFFFFFFFF); } else { end = lastElement(node).getEnd() + 1; } return end; } /** * @return qualifier and type name */ public static String[] splitName(ClassNode node) { String name = node.getName(); int index = name.lastIndexOf('$'); if (index == -1) index = name.lastIndexOf('.'); return new String[] {name.substring(0, Math.max(0, index)), name.substring(index + 1)}; } public static ClassNode getBaseType(ClassNode node) { while (node.isArray()) node = node.getComponentType(); return node; } public static GenericsType[] getGenericsTypes(ClassNode classNode) { GenericsType[] generics = getBaseType(classNode).getGenericsTypes(); if (generics == null) return GenericsType.EMPTY_ARRAY; return generics; } public static GenericsType[] getGenericsTypes(MethodNode methodNode) { GenericsType[] generics = methodNode.getGenericsTypes(); if (generics == null) return GenericsType.EMPTY_ARRAY; return generics; } public static List<ClassNode> getParameterTypes(Parameter... params) { final int n = params.length; if (n == 0) return Collections.emptyList(); List<ClassNode> types = new ArrayList<ClassNode>(n); for (Parameter param : params) { types.add(param.getType()); } return types; } public static ClassNode[] getTypeParameterBounds(ClassNode typeParam) { if (typeParam.isGenericsPlaceHolder()) { assert typeParam.isUsingGenerics(); GenericsType[] generics = typeParam.getGenericsTypes(); if (generics != null && generics.length > 0) { ClassNode[] bounds = generics[0].getUpperBounds(); if (bounds != null) { return bounds; } } } return ClassNode.EMPTY_ARRAY; } /** * Creates a type signature string for the specified class node, including * its generics if any are present. * * @see org.eclipse.jdt.core.Signature */ public static String getTypeSignature(ClassNode node, boolean qualified, boolean resolved) { StringBuilder builder = new StringBuilder(getTypeSignatureWithoutGenerics(node, qualified, resolved)); if (getBaseType(node).isUsingGenerics() && !getBaseType(node).isGenericsPlaceHolder()) { GenericsType[] generics = getGenericsTypes(node); if (generics.length > 0) { builder.setCharAt(builder.length() - 1, Signature.C_GENERIC_START); for (GenericsType gen : generics) { if (gen.isPlaceholder() || !gen.isWildcard()) { // TODO: Can lower bound or upper bounds exist in this case? builder.append(getTypeSignature(gen.getType(), qualified, resolved)); } else if (gen.getLowerBound() != null) { builder.append(Signature.C_SUPER).append(getTypeSignature(gen.getLowerBound(), qualified, resolved)); } else if (gen.getUpperBounds() != null && gen.getUpperBounds().length > 0) { // TODO: handle more than one builder.append(Signature.C_EXTENDS).append(getTypeSignature(gen.getUpperBounds()[0], qualified, resolved)); } else { builder.append(Signature.C_STAR); } } builder.append(Signature.C_GENERIC_END).append(Signature.C_NAME_END); } } return builder.toString(); } public static String getTypeSignatureWithoutGenerics(ClassNode node, boolean qualified, boolean resolved) { StringBuilder builder = new StringBuilder(); while (node.isArray()) { builder.append('['); node = node.getComponentType(); } String name = node.getName(); if (node.isGenericsPlaceHolder()) { // use "T" instead of "Object" name = node.getUnresolvedName(); } else if (!qualified) { name = node.getNameWithoutPackage(); if (name.indexOf('$') > 0) { name = node.getUnresolvedName(); } } assert !name.startsWith("[") && !name.contains("<") && !name.endsWith(";"); final int pos = builder.length(); builder.append(Signature.createTypeSignature(name, resolved)); if (resolved && node.isGenericsPlaceHolder()) builder.setCharAt(pos, 'T'); return builder.toString(); } public static ClassNode getWrapperTypeIfPrimitive(ClassNode type) { if (ClassHelper.isPrimitiveType(type)) { return ClassHelper.getWrapper(type); } return type; } public static List<ImportNode> getAllImportNodes(ModuleNode moduleNode) { List<ImportNode> importNodes = new ArrayList<ImportNode>(); importNodes.addAll(moduleNode.getImports()); importNodes.addAll(moduleNode.getStarImports()); importNodes.addAll(moduleNode.getStaticImports().values()); importNodes.addAll(moduleNode.getStaticStarImports().values()); // order imports by source position Collections.sort(importNodes, new Comparator<ImportNode>() { public int compare(ImportNode in1, ImportNode in2) { return in1.getEnd() - in2.getEnd(); } }); return importNodes; } public static Expression getTraitFieldExpression(MethodCallExpression call) { if (call.getObjectExpression().getType().getName().endsWith("$Trait$FieldHelper")) { Matcher m = Pattern.compile(".+__(\\w+)\\$[gs]et").matcher(call.getMethodAsString()); if (m.matches()) { String fieldName = m.group(1); @SuppressWarnings("unchecked") List<FieldNode> traitFields = (List<FieldNode>) call.getObjectExpression().getType().getOuterClass().getNodeMetaData("trait.fields"); for (FieldNode field : traitFields) { if (field.getName().equals(fieldName)) { VariableExpression expr = new VariableExpression(field); expr.setSourcePosition(call); return expr; } } } } return null; } /** * Determines if a value or reference of given source type can be assigned * to a receiver of given target type. Excludes checks for the null value, * certain Groovy coercions, etc. * * @see org.codehaus.groovy.classgen.Verifier#isAssignable(ClassNode, ClassNode) * @see org.codehaus.groovy.runtime.MetaClassHelper#isAssignableFrom(Class, Class) * @see org.eclipse.jdt.groovy.search.SimpleTypeLookup#isTypeCompatible(ClassNode, ClassNode) */ public static boolean isAssignable(ClassNode source, ClassNode target) { return isAssignable(false, source, target); } private static boolean isAssignable(boolean array, ClassNode source, ClassNode target) { if (source.isArray() && target.isArray()) { return isAssignable(true, source.getComponentType(), target.getComponentType()); } if ((source.isArray() && (!target.equals(ClassHelper.OBJECT_TYPE) && !target.isGenericsPlaceHolder())) || target.isArray()) { return false; } boolean result; /*if (source.hasClass() && target.hasClass()) { // this matches primitives more thoroughly, but getTypeClass can fail if class has not been loaded result = MetaClassHelper.isAssignableFrom(target.getTypeClass(), source.getTypeClass()); } else*/ if (target.isInterface()) { result = source.equals(target) || source.implementsInterface(target); } else if (array) { // Object or Object[] is universal receiver for an array result = ClassHelper.OBJECT_TYPE.equals(target) || source.isDerivedFrom(target); } else { result = getWrapperTypeIfPrimitive(source).isDerivedFrom(getWrapperTypeIfPrimitive(target)); } // if target is like <T extends A & B>, check source against B final ClassNode[] bounds = getTypeParameterBounds(target); for (int i = 1; i < bounds.length && result; i += 1) { result = isAssignable(array, source, bounds[i]); } return result; } public static void updateClosureWithInferredTypes(ClassNode closure, ClassNode returnType, Parameter[] parameters) { if (!"groovy.lang.Closure".equals(closure.getName()) || closure == closure.redirect()) { return; } //ClassNode redir = // closure.redirect(); //closure.setRedirect(null); //closure.setInterfaces(redir.getInterfaces()); //ReflectionUtils.setPrivateField(ClassNode.class, "clazz", closure, redir.getTypeClass()); //ReflectionUtils.setPrivateField(ClassNode.class, "lazyInitDone", closure, Boolean.FALSE); closure.setGenericsTypes(new GenericsType[] {new GenericsType(getWrapperTypeIfPrimitive(returnType))}); } }