/* * FindBugs - Find Bugs in Java programs * Copyright (C) 2006, University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.ba.generic; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.annotation.CheckForNull; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.ReferenceType; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.type.NullType; import edu.umd.cs.findbugs.util.Util; /** * Utilities for adding support for generics. Most of these methods can be * applied to generic and non generic type information. * * @author Nat Ayewah */ public class GenericUtilities { public static enum TypeCategory { /** A simple (non-generic ObjectType) */ PLAIN_OBJECT_TYPE { @Override public ReferenceType produce(GenericObjectType obj) { return obj; } @Override public String asString(GenericObjectType obj) { // obj.getTypeCategory() does not return PLAIN_OBJECT_TYPE return GenericUtilities.getString(obj); } }, /** A array */ ARRAY_TYPE { @Override public ReferenceType produce(GenericObjectType obj) { return obj; } @Override public String asString(GenericObjectType obj) { // obj.getTypeCategory() does not return ARRAY_TYPE return GenericUtilities.getString(obj); } }, /** A parameterized class e.g. <code>List<String></code> */ PARAMETERIZED { @Override public ReferenceType produce(GenericObjectType obj) { return obj; } @Override public String asString(GenericObjectType obj) { StringBuilder b = new StringBuilder(obj.toPlainString()); b.append("<"); boolean first = true; for (Type t : obj.parameters) { if (!first) b.append(","); first = false; b.append(GenericUtilities.getString(t)); } b.append(">"); return b.toString(); } }, /** * A simple type variable e.g. <code>E</code>. Underlying ObjectType is * <code>java.lang.Object</code> */ TYPE_VARIABLE { @Override public ReferenceType produce(GenericObjectType obj) { return Type.OBJECT; } @Override public String asString(GenericObjectType obj) { return obj.variable; } }, /** * A simple wildcard i.e. <code>?</code>. Underlying ObjectType is * <code>java.lang.Object</code> */ WILDCARD { @Override public ReferenceType produce(GenericObjectType obj) { return Type.OBJECT; } @Override public String asString(GenericObjectType obj) { return "?"; } }, /** * A wildcard that extends another ObjectType e.g. * <code>? extends Comparable</code>. Underlying ObjectType is * <code>java.lang.Object</code>. The extended type can be an ObjectType * or a GenericObjectType */ WILDCARD_EXTENDS { @Override public ReferenceType produce(GenericObjectType obj) { return obj.extension; } @Override public String asString(GenericObjectType obj) { Type extension = obj.extension; assert extension != null; return "? extends " + GenericUtilities.getString(extension); } }, /** * A wildcard that is extended by another ObjectType e.g. * <code>? super Comparable</code>. Underlying ObjectType is * <code>java.lang.Object</code>. The super type can be an ObjectType or * a GenericObjectType */ WILDCARD_SUPER { @Override public ReferenceType produce(GenericObjectType obj) { return Type.OBJECT; } @Override public String asString(GenericObjectType obj) { Type extension = obj.extension; assert extension != null; return "? super " + GenericUtilities.getString(extension); } }; public abstract String asString(GenericObjectType obj); public abstract ReferenceType produce(GenericObjectType obj); public static String asString(ArrayType atype) { Type obj = atype.getBasicType(); String result = GenericUtilities.getString(obj); return result + Util.repeat("[]", atype.getDimensions()); } } /** * Get the TypeCategory that represents this Object * * @see GenericUtilities.TypeCategory */ public static final TypeCategory getTypeCategory(Type type) { if (type instanceof GenericObjectType) return ((GenericObjectType) type).getTypeCategory(); if (type instanceof ObjectType || type instanceof NullType) return TypeCategory.PLAIN_OBJECT_TYPE; if (type instanceof ArrayType) return TypeCategory.ARRAY_TYPE; throw new IllegalArgumentException("Not a reference type: " + type); } public static final boolean isPlainObject(Type type) { return getTypeCategory(type) == TypeCategory.PLAIN_OBJECT_TYPE; } /** * Get String representation of a Type including Generic information */ public static final String getString(Type type) { if (type instanceof GenericObjectType) return ((GenericObjectType) type).toString(true); else if (type instanceof ArrayType) return TypeCategory.asString((ArrayType) type); else return type.toString(); } static String stripAngleBrackets(String s) { if (s.indexOf('<') == -1) return s; StringBuilder result = new StringBuilder(s.length()); int nesting = 0; boolean seenLeftBracket = false; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '<') { nesting++; seenLeftBracket = true; } else if (c == '>') nesting--; else if (nesting == 0) { if (seenLeftBracket && c == '.') result.append('$'); else result.append(c); } } return result.toString(); } public static GenericObjectType getType(String className, List<? extends ReferenceType> parameters) { return new GenericObjectType(className, parameters); } /** * This method is analogous to <code>Type.getType(String)</code>, except * that it also accepts signatures with generic information. e.g. * <code>Ljava/util/ArrayList<TT;>;</code> * <p> * * The signature should only contain one type. Use GenericSignatureParser to * break up a signature with many types or call createTypes(String) to * return a list of types */ public static @CheckForNull Type getType(String signature) { try { // ensure signature only has one type if (new GenericSignatureParser("(" + signature + ")V").getNumParameters() != 1) throw new IllegalArgumentException("the following signature does not " + "contain exactly one type: " + signature); int index = 0; if (signature.startsWith("L")) { index = lastMatchedLeftAngleBracket(signature); if (index < 0) return Type.getType(stripAngleBrackets(signature)); String typeParameters = signature.substring(index + 1, nextUnmatchedRightAngleBracket(signature, index + 1)); List<ReferenceType> parameters = GenericUtilities.getTypeParameters(typeParameters); if (parameters == null) return null; String baseType = removeMatchedAngleBrackets(signature.substring(1, index)).replace('.', '$'); return new GenericObjectType(baseType, parameters); } else if (signature.startsWith("T")) { // can't handle type variables return null; } else if (signature.startsWith("[")) { index++; while (signature.charAt(index) == '[') index++; Type componentType = getType(signature.substring(index)); if (componentType == null) return null; return new ArrayType(componentType, index); } else if (signature.startsWith("*")) { return new GenericObjectType("*"); } else if (signature.startsWith("+") || signature.startsWith("-")) { Type baseType = getType(signature.substring(1)); if (baseType == null) return null; return new GenericObjectType(signature.substring(0, 1), (ReferenceType) baseType); } else // assert signature contains no generic information return Type.getType(signature); } catch (IllegalStateException e) { AnalysisContext.logError("Error parsing signature " + signature, e); return null; } } public static ObjectType merge(@CheckForNull Type t1, ObjectType t2) { if (t1 instanceof GenericObjectType) return merge((GenericObjectType) t1, t2); return t2; } public static Type merge(@CheckForNull GenericObjectType t1, Type t2) { if (t1 == null) return t2; if (t2 instanceof ObjectType) return merge(t1,(ObjectType) t2); if (t2 instanceof NullType) return t1; return t2; } public static ObjectType merge(@CheckForNull GenericObjectType t1, ObjectType t2) { if (t1 == null || t2 instanceof GenericObjectType) return t2; List<? extends ReferenceType> parameters = t1.getParameters(); if (parameters == null) return t2; return new GenericObjectType(t2.getClassName(), parameters); } public static String removeMatchedAngleBrackets(String s) { int first = s.indexOf('<'); if (first < 0) return s; StringBuilder result = new StringBuilder(s.substring(0, first)); int pos = first; int nesting = 0; while (pos < s.length()) { char c = s.charAt(pos++); if (c == '<') nesting++; else if (c == '>') nesting--; else if (nesting == 0) result.append(c); } return result.toString(); } public static int nextUnmatchedRightAngleBracket(String s, int startingAt) { int nesting = 0; int pos = startingAt; while (true) { if (pos < 0) return -1; char c = s.charAt(pos); if (c == '>') { if (nesting == 0) return pos; nesting--; } else if (c == '<') nesting++; pos++; } } public static int lastMatchedLeftAngleBracket(String s) { int nesting = 0; int pos = s.length() - 2; while (true) { if (pos < 0) return -1; char c = s.charAt(pos); if (c == '<') { nesting--; if (nesting == 0) return pos; } else if (c == '>') nesting++; else if (nesting == 0) return -1; pos--; } } /** * Parse a bytecode signature that has 1 or more (possibly generic) types * and return a list of the Types. * * @param signature * bytecode signature e.g. e.g. * <code>Ljava/util/ArrayList<Ljava/lang/String;>;Ljava/util/ArrayList<TT;>;Ljava/util/ArrayList<*>;</code> */ public static final @CheckForNull List<ReferenceType> getTypeParameters(String signature) { GenericSignatureParser parser = new GenericSignatureParser("(" + signature + ")V"); List<ReferenceType> types = new ArrayList<ReferenceType>(); Iterator<String> iter = parser.parameterSignatureIterator(); while (iter.hasNext()) { String parameterString = iter.next(); ReferenceType t = (ReferenceType) getType(parameterString); if (t == null) return null; types.add(t); } return types; } public static final List<String> split(String signature, boolean skipInitialAngleBracket) { List<String> result = new ArrayList<String>(); if (signature.charAt(0) != '<') skipInitialAngleBracket = false; int depth = 0; int start = 0; for (int pos = start; pos < signature.length(); pos++) switch (signature.charAt(pos)) { case '<': depth++; break; case '>': depth--; if (depth == 0 && skipInitialAngleBracket) { skipInitialAngleBracket = false; start = pos + 1; } break; case ';': if (depth > 0) break; String substring = signature.substring(start, pos + 1); result.add(substring); start = pos + 1; } if (depth != 0) throw new IllegalArgumentException("Unbalanced signature: " + signature); return result; } }