001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.bcel.classfile; 018 019import java.io.ByteArrayInputStream; 020import java.io.DataInput; 021import java.io.DataOutputStream; 022import java.io.IOException; 023import java.nio.charset.StandardCharsets; 024import java.util.Objects; 025 026import org.apache.bcel.Const; 027import org.apache.bcel.util.Args; 028 029/** 030 * This class is derived from <em>Attribute</em> and represents a reference to a GJ attribute. 031 * 032 * @see Attribute 033 */ 034public final class Signature extends Attribute { 035 036 /** 037 * Extends ByteArrayInputStream to make 'unreading' chars possible. 038 */ 039 private static final class MyByteArrayInputStream extends ByteArrayInputStream { 040 041 MyByteArrayInputStream(final String data) { 042 super(data.getBytes(StandardCharsets.UTF_8)); 043 } 044 045 String getData() { 046 return new String(buf, StandardCharsets.UTF_8); 047 } 048 049 void unread() { 050 if (pos > 0) { 051 pos--; 052 } 053 } 054 } 055 056 private static boolean identStart(final int ch) { 057 return ch == 'T' || ch == 'L'; 058 } 059 060 // @since 6.0 is no longer final 061 public static boolean isActualParameterList(final String s) { 062 return s.startsWith("L") && s.endsWith(">;"); 063 } 064 065 // @since 6.0 is no longer final 066 public static boolean isFormalParameterList(final String s) { 067 return s.startsWith("<") && s.indexOf(':') > 0; 068 } 069 070 private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf) { 071 int ch; 072 matchIdent(in, buf); 073 ch = in.read(); 074 if (ch == '<' || ch == '(') { // Parameterized or method 075 // System.out.println("Enter <"); 076 buf.append((char) ch); 077 matchGJIdent(in, buf); 078 while ((ch = in.read()) != '>' && ch != ')') { // List of parameters 079 if (ch == -1) { 080 throw new IllegalArgumentException("Illegal signature: " + in.getData() + " reaching EOF"); 081 } 082 // System.out.println("Still no >"); 083 buf.append(", "); 084 in.unread(); 085 matchGJIdent(in, buf); // Recursive call 086 } 087 // System.out.println("Exit >"); 088 buf.append((char) ch); 089 } else { 090 in.unread(); 091 } 092 ch = in.read(); 093 if (identStart(ch)) { 094 in.unread(); 095 matchGJIdent(in, buf); 096 } else if (ch == ')') { 097 in.unread(); 098 } else if (ch != ';') { 099 throw new IllegalArgumentException("Illegal signature: " + in.getData() + " read " + (char) ch); 100 } 101 } 102 103 private static void matchIdent(final MyByteArrayInputStream in, final StringBuilder buf) { 104 int ch; 105 if ((ch = in.read()) == -1) { 106 throw new IllegalArgumentException("Illegal signature: " + in.getData() + " no ident, reaching EOF"); 107 } 108 // System.out.println("return from ident:" + (char) ch); 109 if (!identStart(ch)) { 110 final StringBuilder buf2 = new StringBuilder(); 111 int count = 1; 112 while (Character.isJavaIdentifierPart((char) ch)) { 113 buf2.append((char) ch); 114 count++; 115 ch = in.read(); 116 } 117 if (ch == ':') { // Ok, formal parameter 118 final int skipExpected = "Ljava/lang/Object".length(); 119 final long skipActual = in.skip(skipExpected); 120 if (skipActual != skipExpected) { 121 throw new IllegalStateException(String.format("Unexpected skip: expected=%,d, actual=%,d", skipExpected, skipActual)); 122 } 123 buf.append(buf2); 124 ch = in.read(); 125 in.unread(); 126 // System.out.println("so far:" + buf2 + ":next:" +(char) ch); 127 } else { 128 for (int i = 0; i < count; i++) { 129 in.unread(); 130 } 131 } 132 return; 133 } 134 final StringBuilder buf2 = new StringBuilder(); 135 ch = in.read(); 136 do { 137 buf2.append((char) ch); 138 ch = in.read(); 139 // System.out.println("within ident:"+ (char) ch); 140 } while (ch != -1 && (Character.isJavaIdentifierPart((char) ch) || ch == '/')); 141 buf.append(Utility.pathToPackage(buf2.toString())); 142 // System.out.println("regular return ident:"+ (char) ch + ":" + buf2); 143 if (ch != -1) { 144 in.unread(); 145 } 146 } 147 148 public static String translate(final String s) { 149 // System.out.println("Sig:" + s); 150 final StringBuilder buf = new StringBuilder(); 151 matchGJIdent(new MyByteArrayInputStream(s), buf); 152 return buf.toString(); 153 } 154 155 private int signatureIndex; 156 157 /** 158 * Constructs object from file stream. 159 * 160 * @param nameIndex Index in constant pool to CONSTANT_Utf8 161 * @param length Content length in bytes 162 * @param input Input stream 163 * @param constantPool Array of constants 164 * @throws IOException if an I/O error occurs. 165 */ 166 Signature(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException { 167 this(nameIndex, length, input.readUnsignedShort(), constantPool); 168 } 169 170 /** 171 * @param nameIndex Index in constant pool to CONSTANT_Utf8 172 * @param length Content length in bytes 173 * @param signatureIndex Index in constant pool to CONSTANT_Utf8 174 * @param constantPool Array of constants 175 */ 176 public Signature(final int nameIndex, final int length, final int signatureIndex, final ConstantPool constantPool) { 177 super(Const.ATTR_SIGNATURE, nameIndex, Args.require(length, 2, "Signature length attribute"), constantPool); 178 this.signatureIndex = signatureIndex; 179 // validate: 180 Objects.requireNonNull(constantPool.getConstantUtf8(signatureIndex), "constantPool.getConstantUtf8(signatureIndex)"); 181 } 182 183 /** 184 * Initialize from another object. Note that both objects use the same references (shallow copy). Use clone() for a 185 * physical copy. 186 * 187 * @param c Source to copy. 188 */ 189 public Signature(final Signature c) { 190 this(c.getNameIndex(), c.getLength(), c.getSignatureIndex(), c.getConstantPool()); 191 } 192 193 /** 194 * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. 195 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. 196 * 197 * @param v Visitor object 198 */ 199 @Override 200 public void accept(final Visitor v) { 201 // System.err.println("Visiting non-standard Signature object"); 202 v.visitSignature(this); 203 } 204 205 /** 206 * @return deep copy of this attribute 207 */ 208 @Override 209 public Attribute copy(final ConstantPool constantPool) { 210 return (Attribute) clone(); 211 } 212 213 /** 214 * Dump source file attribute to file stream in binary format. 215 * 216 * @param file Output file stream 217 * @throws IOException if an I/O error occurs. 218 */ 219 @Override 220 public void dump(final DataOutputStream file) throws IOException { 221 super.dump(file); 222 file.writeShort(signatureIndex); 223 } 224 225 /** 226 * @return GJ signature. 227 */ 228 public String getSignature() { 229 return super.getConstantPool().getConstantUtf8(signatureIndex).getBytes(); 230 } 231 232 /** 233 * @return Index in constant pool of source file name. 234 */ 235 public int getSignatureIndex() { 236 return signatureIndex; 237 } 238 239 /** 240 * @param signatureIndex the index info the constant pool of this signature 241 */ 242 public void setSignatureIndex(final int signatureIndex) { 243 this.signatureIndex = signatureIndex; 244 } 245 246 /** 247 * @return String representation 248 */ 249 @Override 250 public String toString() { 251 return "Signature: " + getSignature(); 252 } 253}