content: /*
   * Copyright 2019 Contributors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      https://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.springframework.boot.graal.type;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Enables us to check things quickly in the constant pool. Just parses the class up to the end of the constant pool.
   *
   * Useful reference: https://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
   *
   * @author Andy Clement
   */
  public class ConstantPoolScanner {
  
  	private static final boolean DEBUG = false;
  
  	private final static byte CONSTANT_Utf8 = 1;
  
  	private final static byte CONSTANT_Integer = 3;
  
  	private final static byte CONSTANT_Float = 4;
  
  	private final static byte CONSTANT_Long = 5;
  
  	private final static byte CONSTANT_Double = 6;
  
  	private final static byte CONSTANT_Class = 7;
  
  	private final static byte CONSTANT_String = 8;
  
  	private final static byte CONSTANT_Fieldref = 9;
  
  	private final static byte CONSTANT_Methodref = 10;
  
  	private final static byte CONSTANT_InterfaceMethodref = 11;
  
  	private final static byte CONSTANT_NameAndType = 12;
  
  	private final static byte CONSTANT_MethodHandle = 15;
  
  	private final static byte CONSTANT_MethodType = 16;
  
  	private final static byte CONSTANT_InvokeDynamic = 18;
  
  	private byte[] classbytes;
  
  	// Used during the parse step
  	private int ptr;
  
  	// Filled with strings and int[]
  	private Object[] cpdata;
  
  	private int cpsize;
  
  	private int[] type;
  
  	// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
  	private List<String> referencedClasses = new ArrayList<String>();
  
  	private List<String> referencedMethods = new ArrayList<String>();
  
  	private String slashedclassname;
  
  
  	public static References getReferences(byte[] classbytes) {
  		ConstantPoolScanner cpScanner = new ConstantPoolScanner(classbytes);
  		return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods);
  	}
  	
  	public static References getReferences(File f) {
  		try {
  			byte[] bytes = Files.readAllBytes(Paths.get(f.toURI()));
  			ConstantPoolScanner cpScanner = new ConstantPoolScanner(bytes);
  			return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods);
  		} catch (IOException e) {
  			throw new IllegalStateException(e);
  		}
  	}
  	
  	public ConstantPoolScanner(File f) {
  		this(readBytes(f));
  	}
  	
  	public static byte[] readBytes(File f) {
  		try {
  			byte[] bytes = Files.readAllBytes(Paths.get(f.toURI()));
  			return bytes;
  		} catch (IOException e) {
  			throw new IllegalStateException(e);
  		}
  	}
  
  	private ConstantPoolScanner(byte[] bytes) {
  		parseClass(bytes);
  //		computeReferences();
  	}
  
  	// Format of a classfile:
  	//	ClassFile {
  	//    	u4 magic;
  	//    	u2 minor_version;
  	//    	u2 major_version;
  	//    	u2 constant_pool_count;
  	//    	cp_info constant_pool[constant_pool_count-1];
  	//    	u2 access_flags;
  	//    	u2 this_class;
  	//    	u2 super_class;
  	//    	u2 interfaces_count;
  	//    	u2 interfaces[interfaces_count];
  	//    	u2 fields_count;
  	//    	field_info fields[fields_count];
  	//    	u2 methods_count;
  	//    	method_info methods[methods_count];
  	//    	u2 attributes_count;
  	//    	attribute_info attributes[attributes_count];
  	//    }
  	private void parseClass(byte[] bytes) {
  		try {
  			this.classbytes = bytes;
  			this.ptr = 0;
  			int magic = readInt(); // magic 0xCAFEBABE
  			if (magic != 0xCAFEBABE) {
  				throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
  			}
  			ptr += 4; // skip minor and major versions
  			cpsize = readUnsignedShort();
  			if (DEBUG) {
  				System.out.println("Constant Pool Size =" + cpsize);
  			}
  			cpdata = new Object[cpsize];
  			type = new int[cpsize];
  			for (int cpentry = 1; cpentry < cpsize; cpentry++) {
  				boolean wasDoubleSlotItem = processConstantPoolEntry(cpentry);
  				if (wasDoubleSlotItem) {
  					cpentry++;
  				}
  			}
  			ptr += 2; // access flags
  			int thisclassname = readUnsignedShort();
  			int classindex = ((Integer) cpdata[thisclassname]);
  			slashedclassname = accessUtf8(classindex);
  		}
  		catch (Exception e) {
  			throw new IllegalStateException("Unexpected problem processing bytes for class", e);
  		}
  	}
  	
  	public String getClassname() {
  		return slashedclassname;
  	}
  
  	/**
  	 * Return the UTF8 at the specified index in the constant pool. The data found at the constant pool for that index
  	 * may not have been unpacked yet if this is the first access of the string. If not unpacked the constant pool entry
  	 * is a pair of ints in an array representing the offset and length within the classbytes where the UTF8 string is
  	 * encoded. Once decoded the constant pool entry is flipped from an int array to a String for future fast access.
  	 *
  	 * @param cpIndex constant pool index
  	 * @return UTF8 string at that constant pool index
  	 */
  	private String accessUtf8(int cpIndex) {
  		Object object = cpdata[cpIndex];
  		if (object instanceof String) {
  			return (String) object;
  		}
  		int[] ptrAndLen = (int[]) object;
  		String value;
  		try {
  			value = new String(classbytes, ptrAndLen[0], ptrAndLen[1], "UTF8");
  		}
  		catch (UnsupportedEncodingException e) {
  			throw new IllegalStateException("Bad data found at constant pool position " + cpIndex + " offset="
  					+ ptrAndLen[0] + " length=" + ptrAndLen[1], e);
  		}
  		cpdata[cpIndex] = value;
  		return value;
  	}
  
  	/**
  	 * @return an int constructed from the next four bytes to be processed
  	 */
  	private final int readInt() {
  		return ((classbytes[ptr++] & 0xFF) << 24) + ((classbytes[ptr++] & 0xFF) << 16)
  				+ ((classbytes[ptr++] & 0xFF) << 8)
  				+ (classbytes[ptr++] & 0xFF);
  	}
  
  	/**
  	 * @return an unsigned short constructed from the next two bytes to be processed
  	 */
  	private final int readUnsignedShort() {
  		return ((classbytes[ptr++] & 0xff) << 8) + (classbytes[ptr++] & 0xff);
  	}
  
  	private boolean processConstantPoolEntry(int index) throws IOException {
  		byte b = classbytes[ptr++];
  		switch (b) {
  			case CONSTANT_Integer: // CONSTANT_Integer_info { u1 tag; u4 bytes; }
  			case CONSTANT_Float: // CONSTANT_Float_info { u1 tag; u4 bytes; }
  			case CONSTANT_Fieldref: // CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
  			case CONSTANT_InterfaceMethodref: // CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
  			case CONSTANT_InvokeDynamic: // CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; }
  				ptr += 4;
  				break;
  			case CONSTANT_Utf8:
  				// CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
  				// Cache just the index and length - do not unpack it now
  				int len = readUnsignedShort();
  				cpdata[index] = new int[] { ptr, len };
  				ptr += len;
  				break;
  			case CONSTANT_Long: // CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; }
  			case CONSTANT_Double: // CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; }
  				ptr += 8;
  				return true;
  			case CONSTANT_Class: // CONSTANT_Class_info { u1 tag; u2 name_index; }
  				type[index] = b;
  				cpdata[index] = readUnsignedShort();
  				break;
  			case CONSTANT_Methodref:
  				// CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
  				type[index] = b;
  				cpdata[index] = new int[] { readUnsignedShort(), readUnsignedShort() };
  				break;
  			case CONSTANT_NameAndType:
  				// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
  				// CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
  				//			type[index] = b;
  				cpdata[index] = readUnsignedShort();
  				ptr += 2; // skip the descriptor for now
  				break;
  			case CONSTANT_MethodHandle:
  				// CONSTANT_MethodHandle_info { u1 tag; u1 reference_kind; u2 reference_index; }
  				ptr += 3;
  				break;
  			case CONSTANT_String: // CONSTANT_String_info { u1 tag; u2 string_index; }
  			case CONSTANT_MethodType: // CONSTANT_MethodType_info { u1 tag; u2 descriptor_index; }
  				ptr += 2;
  				break;
  			default:
  				throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
  		}
  		return false;
  	}
  
  	private void computeReferences() {
  		for (int i = 0; i < cpsize; i++) {
  			switch (type[i]) {
  				case CONSTANT_Class:
  					int classindex = ((Integer) cpdata[i]);
  					String classname = accessUtf8(classindex);
  					if (classname == null) {
  						throw new IllegalStateException();
  					}
  					referencedClasses.add(classname);
  					break;
  				case CONSTANT_Methodref:
  					int[] indexes = (int[]) cpdata[i];
  					int classindex2 = indexes[0];
  					int nameAndTypeIndex = indexes[1];
  					StringBuilder s = new StringBuilder();
  					String theClassName = accessUtf8((Integer) cpdata[classindex2]);
  					if (theClassName.charAt(0) == 'j') {
  						s.append(theClassName);
  						s.append(".");
  						s.append(accessUtf8((Integer) cpdata[nameAndTypeIndex]));
  						referencedMethods.add(s.toString());
  					}
  					break;
  				//			private final static byte CONSTANT_Utf8 = 1;
  				//			private final static byte CONSTANT_Integer = 3;
  				//			private final static byte CONSTANT_Float = 4;
  				//			private final static byte CONSTANT_Long = 5;
  				//			private final static byte CONSTANT_Double = 6;
  				//			private final static byte CONSTANT_String = 8;
  				//			private final static byte CONSTANT_Fieldref = 9;
  				//			private final static byte CONSTANT_InterfaceMethodref = 11;
  				//			private final static byte CONSTANT_NameAndType = 12;
  			}
  		}
  	}
  
  
  	public static class References {
  
  		public final String slashedClassName;
  
  		private final List<String> referencedClasses;
  
  		private final List<String> referencedMethods;
  
  		References(String slashedClassName, List<String> rc, List<String> rm) {
  			this.slashedClassName = slashedClassName;
  			this.referencedClasses = rc;
  			this.referencedMethods = rm;
  		}
  
  		@Override
  		public String toString() {
  			StringBuilder s = new StringBuilder();
  			s.append("Class=").append(slashedClassName).append("\n");
  			s.append("ReferencedClasses=#").append(referencedClasses.size()).append("\n");
  			s.append("ReferencedMethods=#").append(referencedMethods.size()).append("\n");
  			return s.toString();
  		}
  		
  		/**
  		 * @return list of classes of the form <tt>org/springframework/boot/configurationprocessor/json/JSONException</tt>
  		 */
  		public List<String> getReferencedClasses() {
  			return referencedClasses;
  		}
  	}
  
  
  }