/* * 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; import com.jopdesign.common.bcel.Annotation; import com.jopdesign.common.bcel.AnnotationAttribute; import com.jopdesign.common.bcel.AnnotationReader; import com.jopdesign.common.code.CallString; import com.jopdesign.common.misc.JavaClassFormatError; import com.jopdesign.common.type.MemberID; import com.jopdesign.common.type.MethodRef; import org.apache.bcel.Constants; import org.apache.bcel.classfile.AccessFlags; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.Synthetic; import org.apache.bcel.generic.ConstantPoolGen; import java.util.Arrays; /** * @author Stefan Hepp (stefan@stefant.org) */ public abstract class MemberInfo { public enum AccessType { ACC_PUBLIC, ACC_PACKAGE, ACC_PRIVATE, ACC_PROTECTED } private final AccessFlags accessFlags; private final MemberID memberId; private final int hashValue; private Object[] customValues; protected MemberInfo(AccessFlags flags, MemberID memberId) { this.accessFlags = flags; this.memberId = memberId; customValues = null; // cache the hash :) speed up things a little, memberSignature is immutable in BCEL anyway hashValue = memberId.hashCode(); } //////////////////////////////////////////////////////////////////////////// // References to ClassInfo and AppInfo //////////////////////////////////////////////////////////////////////////// /** * Just a convenience method to get the AppInfo instance. * @return the AppInfo singleton. */ public AppInfo getAppInfo() { return AppInfo.getSingleton(); } public abstract ClassInfo getClassInfo(); public abstract String getClassName(); public abstract ConstantPoolGen getConstantPoolGen(); //////////////////////////////////////////////////////////////////////////// // Standard getter and setter, delegates to BCEL //////////////////////////////////////////////////////////////////////////// /** * Get only the last part of the name (i.e. the class name without package or the field/method name * without class prefix and descriptor). * * @return the short name of this member without package- or class prefix and without descriptor. */ public abstract String getShortName(); public boolean isPublic() { return accessFlags.isPublic(); } public boolean isPrivate() { return accessFlags.isPrivate(); } public boolean isProtected() { return accessFlags.isProtected(); } public boolean isFinal() { return accessFlags.isFinal(); } public boolean isStatic() { return accessFlags.isStatic(); } public void setStatic(boolean val) { accessFlags.isStatic(val); } public void setFinal(boolean val) { accessFlags.isFinal(val); } public int getAccessFlags() { return accessFlags.getAccessFlags(); } /** * Get the access type of this object. * @return a value of {@link AccessType}. */ public AccessType getAccessType() { if ( isPublic() ) { return AccessType.ACC_PUBLIC; } if ( isPrivate() ) { return AccessType.ACC_PRIVATE; } if ( isProtected() ) { return AccessType.ACC_PROTECTED; } return AccessType.ACC_PACKAGE; } /** * Set the access type of this object. * @param type the access type to set. */ public void setAccessType(AccessType type) { int af = accessFlags.getAccessFlags() & ~(Constants.ACC_PRIVATE|Constants.ACC_PROTECTED|Constants.ACC_PUBLIC); switch (type) { case ACC_PRIVATE: af |= Constants.ACC_PRIVATE; break; case ACC_PROTECTED: af |= Constants.ACC_PROTECTED; break; case ACC_PUBLIC: af |= Constants.ACC_PUBLIC; break; } accessFlags.setAccessFlags(af); } public String getModifierString() { StringBuffer out = new StringBuffer(); if ( isPrivate() ) { out.append("private "); } if ( isProtected() ) { out.append("protected "); } if ( isPublic() ) { out.append("public "); } if ( accessFlags.isSynchronized() ) { out.append("synchronized "); } if ( isStatic() ) { out.append("static "); } if ( isFinal() ) { out.append("final "); } if ( accessFlags.isAbstract() ) { out.append("abstract "); } return out.toString(); } //////////////////////////////////////////////////////////////////////////// // Custom Values //////////////////////////////////////////////////////////////////////////// public Object removeCustomValue(KeyManager.CustomKey key) { return setCustomValue(key, null); } /** * Sets a new custom info value for a key. * Setting null as value has the same effect as removing the key. * * @param key The key to set the new value for * @param customValue the new value to set, or null to unset the value. * @return the old value, or null if not set previously. */ public Object setCustomValue(KeyManager.CustomKey key, Object customValue) { // We could use generics here, and even use customValue.class as key, but // 1) using class as key makes it impossible to attach the same CustomValue class // with different values multiple times, // 2) using generics like 'public <T extends CustomClassInfo> T getCustomValue() .. ' does // not work since Java removes the generics type-info at compile-time, its not possible // to access T.class or do 'instanceof T' or even 'try { return (T) value; } catch (Exception e) ..', // therefore a possible type conflict must always(!) be handled at the callsite, so we may as well make // the cast explicit at the callsite. if ( key == null ) { return null; } int id = key.getId(); if ( customValues == null ) { customValues = new Object[getAppInfo().getKeyManager().getNumStructKeys()]; } else if ( id >= customValues.length ) { customValues = Arrays.copyOf(customValues, getAppInfo().getKeyManager().getNumStructKeys()); } Object oldVal = customValues[id]; customValues[id] = customValue; return oldVal; } public Object setCustomValue(KeyManager.CustomKey key, CallString context, Object customValue) { return null; } public Object getCustomValue(KeyManager.CustomKey key) { if ( customValues == null || key == null || key.getId() >= customValues.length ) {return null;} return customValues[key.getId()]; } public Object getCustomValue(KeyManager.CustomKey key, CallString context, boolean checkSuffixes) { return null; } public void copyCustomValuesFrom(MemberInfo from) { // TODO implement } //////////////////////////////////////////////////////////////////////////// // Attribute access and helpers for custom attributes //////////////////////////////////////////////////////////////////////////// public void setSynthetic(boolean flag) { // from major version 49 on, ACC_SYNTHETIC is supported Synthetic s = findSynthetic(); if ( getClassInfo().getMajor() < 49 ) { if ( flag ) { if ( s == null ) { ConstantPoolGen cpg = getClassInfo().getConstantPoolGen(); int index = cpg.addUtf8("Synthetic"); addAttribute(new Synthetic(index, 0, new byte[0], cpg.getConstantPool())); } } else { if ( s != null ) { removeAttribute(s); } } } else { accessFlags.isSynthetic(flag); if ( !flag && s != null ) { removeAttribute(s); } } } public boolean isSynthetic() { if (accessFlags.isSynthetic()) { return true; } Synthetic s = findSynthetic(); return s != null; } public void setDeprecated(boolean flag) { if (flag) { if (findDeprecated() == null) { ConstantPoolGen cpg = getClassInfo().getConstantPoolGen(); int index = cpg.addUtf8("Deprecated"); addAttribute(new org.apache.bcel.classfile.Deprecated(index, 0, new byte[0], cpg.getConstantPool())); } } else { org.apache.bcel.classfile.Deprecated d = findDeprecated(); if ( d != null ) { removeAttribute(d); } } } public boolean isDeprecated() { return findDeprecated() != null; } /** * Get the annotation attribute of this member * @param visible whether to the the visible or invisible annotation attribute (see {@link AnnotationAttribute#isVisible()} * @param create if true, create the attribute if it does not exist * @return the annotation attribute or null if it does not exist and {@code create} is false */ public AnnotationAttribute getAnnotation(boolean visible, boolean create) { for (Attribute a : getAttributes()) { if ( a instanceof AnnotationAttribute ) { if ( ((AnnotationAttribute)a).isVisible() == visible ) { return (AnnotationAttribute) a; } } } if (create) { ConstantPoolGen cpg = getClassInfo().getConstantPoolGen(); String name = visible ? AnnotationReader.VISIBLE_ANNOTATION_NAME : AnnotationReader.INVISIBLE_ANNOTATION_NAME; AnnotationAttribute a = new AnnotationAttribute(cpg.addUtf8(name), 0, cpg.getConstantPool(), visible, 0); a.updateLength(); addAttribute(a); return a; } return null; } public boolean hasAtomicAnnotation() { AnnotationAttribute a = getAnnotation(true, false); // TODO move a.hasAtomicAnnotation implementation here, once JOPizer is ported to the new framework return a != null && a.hasAtomicAnnotation(); } public boolean hasUnusedAnnotation() { AnnotationAttribute a = getAnnotation(false, false); if (a == null) return false; return a.findAnnotation(AnnotationAttribute.UNUSED_TAG_NAME) != null; } public void setUnusedAnnotation() { AnnotationAttribute a = getAnnotation(false, true); if (a.findAnnotation(AnnotationAttribute.UNUSED_TAG_NAME) != null) return; ConstantPoolGen cpg = getConstantPoolGen(); int nameIdx = cpg.addUtf8(AnnotationAttribute.UNUSED_TAG_NAME); Annotation an = new Annotation(nameIdx, cpg.getConstantPool(), 0); a.addAnnotation(an); } public void removeUnusedAnnotation() { AnnotationAttribute a = getAnnotation(false, false); if (a == null) return; Annotation an = a.findAnnotation(AnnotationAttribute.UNUSED_TAG_NAME); if (an != null) { a.removeAnnotation(an); } } public abstract Attribute[] getAttributes(); public abstract void addAttribute(Attribute a); public abstract void removeAttribute(Attribute a); //////////////////////////////////////////////////////////////////////////// // Access checks //////////////////////////////////////////////////////////////////////////// /** * Check if this class or class member can access the given class. * Note that only methods are able to access local classes. * <p> * A member of a class can be accessible due to inheritance, even if the class where it is * defined is not accessible, i.e. if {@code this.canAccess(member)} is true then * {@code this.canAccess(member.getClassInfo())} can be false if * {@code this.getClassInfo().isSubclassOf(member.getClassInfo())}. * </p> * * @see #canAccess(ClassInfo, AccessType) * @param classInfo the class to access. * @return true if this member is able to access the class. */ public boolean canAccess(ClassInfo classInfo) { ClassInfo thisClass = getClassInfo(); if (!classInfo.isNestedClass()) { // Toplevel classes can only be public or package visible, easy to check.. switch (classInfo.getAccessType()) { case ACC_PUBLIC: return true; case ACC_PACKAGE: return thisClass.hasSamePackage(classInfo); default: throw new JavaClassFormatError("Invalid access type "+classInfo.getAccessType() +" of toplevel class "+thisClass.getClassName()); } } // Inner classes (classes within methods) can only be accessed from the same method if ( classInfo.isLocalInnerClass() ) { // we can only access a local class if we are the // direct enclosing method of the local class MethodRef methodRef = classInfo.getEnclosingMethodRef(); return methodRef != null && this.equals(methodRef.getMethodInfo()); } // Access to a member class.. we need to check if we // - can access the enclosing class // - can access the member class in the enclosing class return canAccess(classInfo.getEnclosingClassInfo(), classInfo.getAccessType()); } /** * Check if this class or class member has access to the given class member. * Note that only methods are able to access local classes. * * @param memberInfo the member to access * @return true if this class can access the method or field. */ public boolean canAccess(ClassMemberInfo memberInfo) { return canAccess(memberInfo.getClassInfo(), memberInfo.getAccessType()); } /** * Check if a member of another class with the given accessType can be accessed by this class or this * class member. * Note that only methods are able to access local classes. * * @param cls the class containing the member to check. * @param accessType the accessType of the member to check, as returned by {@link MemberInfo#getAccessType()}. * @return true if this class is allowed to access members of the given accessType of the given class. */ public boolean canAccess(ClassInfo cls, AccessType accessType) { boolean isSubclass = getClassInfo().isSubclassOf(cls); // first, check if we can access the class itself. If we might inherit the member, we do not // need access to the class of the member itself. If we inherit, depends on the modifiers of the member. if (!isSubclass && !canAccess(cls)) { return false; } // now check if we can access the member switch (accessType) { case ACC_PUBLIC: return true; case ACC_PROTECTED: if ( isSubclass ) { return true; } // fallthrough case ACC_PACKAGE: return getClassInfo().hasSamePackage(cls); case ACC_PRIVATE: return cls.getTopLevelClass().equals(getClassInfo().getTopLevelClass()); } return false; } //////////////////////////////////////////////////////////////////////////// // equals, hashCode, MemberId, toString //////////////////////////////////////////////////////////////////////////// /** * Get the memberID object which identifies this member. * @return a fully qualified ID of this member. */ public MemberID getMemberID() { return memberId; } @Override public int hashCode() { return hashValue; } @Override public boolean equals(Object obj) { if (obj==null) return false; if ( !(obj instanceof MemberInfo) ) { return false; } return memberId.equals( ((MemberInfo)obj).getMemberID() ); } @Override public String toString() { return memberId.toString(false); } //////////////////////////////////////////////////////////////////////////// // Private methods //////////////////////////////////////////////////////////////////////////// private Synthetic findSynthetic() { for (Attribute a : getAttributes()) { if ( a instanceof Synthetic ) { return (Synthetic) a; } } return null; } private org.apache.bcel.classfile.Deprecated findDeprecated() { for (Attribute a : getAttributes()) { if ( a instanceof org.apache.bcel.classfile.Deprecated ) { return (org.apache.bcel.classfile.Deprecated) a; } } return null; } }