/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.com/> * * Copyright (C) 2010, Stefan Hepp (stefan@stefant.org). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.common.type; import com.jopdesign.common.AppInfo; import com.jopdesign.common.ClassInfo; import com.jopdesign.common.MemberInfo; import org.apache.bcel.util.ClassPath; import java.io.IOException; /** * This is a (immutable) class to handle parsing, generating, lookups and other signature related tasks * for signatures including classname and/or member names. * * @see Descriptor * @author Stefan Hepp (stefan@stefant.org) */ public class MemberID { // alternative member separator public static final char ALT_MEMBER_SEPARATOR = '#'; private final String className; private final String memberName; private final Descriptor descriptor; private String stringRep = null; /** * Parse a member ID, with or without classname, with or without descriptor. * * @param memberID the member ID to parse. * @param isClassMember If the ID is ambiguous, if true always assume that the last simple member * name is a method or field, else assume it is a class name. * @return the classname part of the ID. */ public static String getClassName(String memberID, boolean isClassMember) { int pos = memberID.indexOf(ALT_MEMBER_SEPARATOR); // uses alternative separator, easy if (pos != -1) return memberID.substring(0, pos); pos = memberID.indexOf('('); if ( pos != -1 ) { // has a descriptor, is a method ID, strip last member part pos = memberID.lastIndexOf('.', pos); return pos != -1 ? memberID.substring(0, pos) : ""; } if (isClassMember) { // field or class name, cannot decide, assume it is a field pos = memberID.lastIndexOf('.'); return pos != -1 ? memberID.substring(0, pos) : ""; } else { // assume it is a class name return memberID; } } public static String getMemberID(String className, String memberName) { return className + "." + memberName; } public static String getMemberID(String className, String memberName, String descriptor) { return className + "." + memberName + descriptor; } public static String getMethodSignature(String memberName, String descriptor) { return memberName + descriptor; } /** * Parse a member ID, with or without classname, with or without descriptor. * If the ID is ambiguous, first check if a class by that name exists, and * if not assume that the signature refers to a class member. * * @param memberID the member ID to parse. * @return a new MemberID object. */ public static MemberID parse(String memberID) { return parse(memberID, false, AppInfo.getSingleton().getClassPath()); } /** * Parse a member ID, with or without classname, with or without descriptor. * * @param memberID the memberID to parse. * @param isClassMember If the ID is ambiguous, if true always assume that the last simple member * name is a method or field, else assume it is a class name. * @return a new MemberID object. */ public static MemberID parse(String memberID, boolean isClassMember) { return parse(memberID, isClassMember, null); } /** * Parse a member ID. * * @param memberID the member ID to parse. * @param classPath if the ID is ambiguous, first check if a class by that name exists in AppInfo or * in this classPath. * @return a new MemberID object. */ public static MemberID parse(String memberID, ClassPath classPath) { return parse(memberID, false, classPath); } /** * Parse a member ID, with or without classname, with or without descriptor. * * @param memberID the member ID to parse. * @param isClassMember If true, always assume that the last simple member * name is a class member, if the ID is ambiguous. * @param classPath If not null and the ID is ambiguous, first check if a class by that name exists, and * if not assume that the ID refers to a class member. Only has an effect if * {@code isClassMember} is {@code false}. * @return a new MemberID object. */ private static MemberID parse(String memberID, boolean isClassMember, ClassPath classPath) { int p1 = memberID.indexOf(ALT_MEMBER_SEPARATOR); int p2 = memberID.indexOf("("); String className = null; String memberName = null; Descriptor descriptor = null; if ( p1 == -1 ) { if ( p2 == -1 ) { // TODO we might want to handle array signatures too here // no descriptor, either not alternative syntax or no classname if ( isClassMember || (classPath != null && !classExists(memberID, classPath)) ) { // is a class member with or without class name p1 = memberID.lastIndexOf('.'); className = p1 != -1 ? memberID.substring(0, p1) : null; memberName = p1 != -1 ? memberID.substring(p1+1) : memberID; } else { // assume signature is classname only className = memberID; } } else { // we have a descriptor, this is a method signature of some sort p1 = memberID.lastIndexOf('.', p2); if (p1 != -1) { className = memberID.substring(0, p1); memberName = memberID.substring(p1+1,p2); } else { memberName = memberID.substring(0,p2); } descriptor = Descriptor.parse(memberID.substring(p2)); } } else { // alternative style with classname, easy to parse className = memberID.substring(0,p1); if ( p2 == -1 ) { memberName = memberID.substring(p1+1); } else { memberName = memberID.substring(p1+1, p2); descriptor = Descriptor.parse(memberID.substring(p2)); } } return new MemberID(className, memberName, descriptor); } private static boolean classExists(String className, ClassPath classPath) { if (AppInfo.getSingleton().hasClassInfo(className)) return true; try { classPath.getClassFile(className); return true; } catch (IOException ignored) { return false; } } /** * Name of a Java class, field or method. Consists of the class name, the member's name and a * description of the member (i.e., its type). * It is permissible to leave out either the class name or the member name. * * @param className name of the Java class. May be {@code null}, if class name is unknown. * Separator must be '.' instead of '/'. * @param memberName name of the class member. If {@code null}, the instance represents a class name. * @param descriptor type of the class member. Must be {@code null}, if the member's name is not given */ public MemberID(String className, String memberName, Descriptor descriptor) { this.className = className; this.memberName = memberName; this.descriptor = descriptor; } public MemberID(String className) { this.className = className; this.memberName = null; this.descriptor = null; } public MemberID(String className, String memberName, String descriptor) { this.className = className; this.memberName = memberName; this.descriptor = Descriptor.parse(descriptor); } public MemberID(String memberName, String descriptor) { this.className = null; this.memberName = memberName; this.descriptor = Descriptor.parse(descriptor); } public MemberID(String memberName, Descriptor descriptor) { this.className = null; this.memberName = memberName; this.descriptor = descriptor; } public boolean isArrayClass() { return className != null && className.startsWith("["); } public boolean hasClassName() { return className != null; } public boolean hasMemberName() { return memberName != null && !"".equals(memberName); } public boolean hasDescriptor() { return descriptor != null; } public boolean hasMethodSignature() { return memberName != null && descriptor != null && descriptor.isMethod(); } public String getClassName() { return className; } /** * @return the member name and descriptor if set and if it is a method descriptor. */ public String getMethodSignature() { return (memberName!=null ? memberName : "") + (descriptor!=null && descriptor.isMethod() ? descriptor : ""); } public String getMemberName() { return memberName; } public Descriptor getDescriptor() { return descriptor; } public MemberInfo findMemberInfo(AppInfo appInfo) { if ( className == null ) { return null; } ClassInfo cls = appInfo.getClassInfo(className); if ( cls == null || !hasMemberName() ) { return cls; } if ( hasMethodSignature() ) { return cls.getMethodInfo(getMethodSignature()); } else { return cls.getFieldInfo(memberName); } } @Override public int hashCode() { int result = className != null ? className.hashCode() : 0; result = 31 * result + (memberName != null ? memberName.hashCode() : 0); result = 31 * result + (descriptor != null ? descriptor.hashCode() : 0); return result; } @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MemberID memberID = (MemberID) o; if (className != null ? !className.equals(memberID.className) : memberID.className != null) return false; if (memberName != null ? !memberName.equals(memberID.memberName) : memberID.memberName != null) return false; if (descriptor != null ? !descriptor.equals(memberID.descriptor) : memberID.descriptor != null) return false; return true; } /** * Get a string representation of this member, using the '#' separator for * class members. This only includes the descriptor for methods, so that * the ID of a field does not include its type. * * @see #toString(boolean) * @return a unique representation of this member ID. */ @Override public String toString() { if (stringRep == null) { stringRep = toString(true); } return stringRep; } /** * String representation of the member ID. Will include the * class name, if present. The descriptor is only appended for methods or if it * is the only component of this ID. * * <p>TODO: Maybe this method should be more general, allowing to specify whether * the signature should include the class name? </p> * * @param altMemberSep Whether to use '#' to separate class name and member signature * @return the ID string */ public String toString(boolean altMemberSep) { StringBuffer s = new StringBuffer(); if ( className != null ) { s.append(className); } if (memberName != null) { if ( className != null ) { s.append(altMemberSep ? ALT_MEMBER_SEPARATOR : '.'); } s.append(memberName); } if ( descriptor != null && ((className == null && memberName == null) || (descriptor.isMethod() && memberName != null) ) ) { s.append(descriptor); } return s.toString(); } }