/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.utility.classfile; import java.io.FilterReader; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.StringReader; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.eclipse.persistence.tools.workbench.utility.classfile.descriptor.FieldType; import org.eclipse.persistence.tools.workbench.utility.classfile.tools.ClassFileDataInputStream; /** * This class models a class file method. * * See "The Java Virtual Machine Specification" Chapter 4. */ public class Method extends Member { private FieldType returnDescriptor; // lazy-initialized - so use the getter private FieldType[] parameterDescriptors; // lazy-initialized - so use the getter private static final String[] EMPTY_STRING_ARRAY = new String[0]; /** constants defined in "The Java Virtual Machine Specification" */ public static final String CONSTRUCTOR_NAME = "<init>"; public static final String STATIC_INITIALIZER_NAME = "<clinit>"; public static final short ACC_BRIDGE = 0x0040; public static final short ACC_VARARGS = 0x0080; /** * cleared bits: * 0x8000 reserved for future use - ignore it * (although the Eclipse compiler sets it for some reason...) * 0x4000 unrecognized by Modifier * 0x2000 unrecognized by Modifier * 0x1000 unrecognized by Modifier * 0x0200 interface * 0x0080 transient * x00040 volatile */ public static final int VISIBLE_ACCESS_FLAGS_MASK = 0x0D3F; /** * Construct a class file method from the specified stream * of byte codes. */ Method(ClassFileDataInputStream stream, MethodPool pool) throws IOException { super(stream, pool); } @Override short visibleAccessFlagsMask() { return VISIBLE_ACCESS_FLAGS_MASK; } @Override public void printDeclarationOn(PrintWriter writer) { if (this.isStaticInitializationMethod()) { writer.print("<static initialization>"); return; } this.printModifierOn(writer); if (this.isConstructor()) { writer.print(this.codeConstructorName()); } else { this.getReturnDescriptor().printDeclarationOn(writer); writer.print(' '); writer.print(this.name()); } writer.print('('); int len = this.getParameterDescriptors().length; for (int i = 0; i < len; i++) { if (i != 0) { writer.write(", "); } this.getParameterDescriptor(i).printDeclarationOn(writer); } writer.print(')'); this.getAttributePool().printThrowsClauseOn(writer); } /** * Return the name that matches the name returned by * java.lang.reflect.Method.getReturnType().getName(). */ public String javaReturnTypeName() { return this.getReturnDescriptor().javaName(); } /** * Return the names that match the names returned by * java.lang.reflect.Method.getParameterTypes()[index].getName(). */ public String[] javaParameterTypeNames() { FieldType[] ptds = this.getParameterDescriptors(); int len = ptds.length; if (len == 0) { return EMPTY_STRING_ARRAY; } String[] names = new String[len]; for (int i = len; i-- > 0; ) { names[i] = ptds[i].javaName(); } return names; } public String[] exceptionClassNames() { return this.getAttributePool().exceptionClassNames(); } public boolean isConstructor() { return this.name().equals(CONSTRUCTOR_NAME); } /** * as opposed to a "declared method" etc. */ public boolean isDeclaredConstructor() { if (this.isSynthetic()) { return false; } return this.isConstructor(); } /** * as opposed to a "declared constructor" etc. */ public boolean isDeclaredMethod() { if (this.isSynthetic()) { return false; } if (this.isConstructor()) { return false; } if (this.isStaticInitializationMethod()) { return false; } return true; } /** * return the name of the constructor as it would be returned * by the reflection api */ public String constructorName() { if ( ! this.isConstructor()) { throw new IllegalStateException(); } return this.classFile().className(); } /** * return the name of the constructor as it would appear in code */ public String codeConstructorName() { if ( ! this.isConstructor()) { throw new IllegalStateException(); } if (this.classFile().isNestedClass()) { // not sure what to return for an "anonymous" class... // currently, this will return "<anonymous>" return this.classFile().nestedClassName(); } String fullName = this.classFile().className(); return fullName.substring(fullName.lastIndexOf('.') + 1); } public boolean isStaticInitializationMethod() { return this.name().equals(STATIC_INITIALIZER_NAME); } /** * Check a bit that cannot (yet?) be interpreted by the * Modifier static methods. This bit indicates the method * is "a bridge method generated by the compiler". */ public boolean isBridge() { return (this.getAccessFlags() & ACC_BRIDGE) != 0; } public boolean isDefault() { return this.classFile().isInterface() && ((getAccessFlags() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC); } /** * Check a bit that cannot (yet?) be interpreted by the * Modifier static methods. This bit indicates the method * "takes a variable number of arguments at the source * code level". */ public boolean isVarArg() { return (this.getAccessFlags() & ACC_VARARGS) != 0; } @Override public void accept(Visitor visitor) { visitor.visit(this); this.getReturnDescriptor().accept(visitor); FieldType[] ptds = this.getParameterDescriptors(); int len = ptds.length; for (int i = 0; i < len; i++) { ptds[i].accept(visitor); } super.accept(visitor); } public MethodPool getMethodPool() { return (MethodPool) this.getPool(); } public FieldType getReturnDescriptor() { if (this.returnDescriptor == null) { this.buildDescriptors(); } return this.returnDescriptor; } public FieldType[] getParameterDescriptors() { if (this.parameterDescriptors == null) { this.buildDescriptors(); } return this.parameterDescriptors; } public FieldType getParameterDescriptor(int index) { if (this.parameterDescriptors == null) { this.buildDescriptors(); } return this.parameterDescriptors[index]; } private void buildDescriptors() { Reader reader = new StringReader(this.descriptor()); try { this.parameterDescriptors = buildParameterDescriptors(reader); this.returnDescriptor = FieldType.createFieldType(reader); } catch (IOException ex) { // this is unlikely when reading a String throw new RuntimeException(ex); } } static FieldType[] buildParameterDescriptors(Reader reader) throws IOException { PeekableReader localReader = new PeekableReader(reader); int c = localReader.read(); if (c != '(') { // not quite sure why this paren is needed - readability? throw new IllegalStateException("open parenthesis expected: " + (char) c); } List parms = new ArrayList(); while (localReader.peek() != ')') { parms.add(FieldType.createFieldType(localReader)); } localReader.read(); // read and discard the closing parenthesis return (FieldType[]) parms.toArray(new FieldType[parms.size()]); } // ********** helper class ********** private static class PeekableReader extends FilterReader { PeekableReader(Reader reader) { super(reader); } /** * Read and return the next character from the * reader without changing the reader's position. */ public int peek() throws IOException { this.mark(1); int peek = this.read(); this.reset(); return peek; } } }