1 /*
  2  * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package jdk.test.lib.classloader;
 24 
 25 import java.io.*;
 26 import java.util.*;
 27 
 28 /**
 29  * Classloader that generates classes on the fly.
 30  *
 31  * This classloader can load classes with name starting with 'Class'. It will
 32  * use TemplateClass as template and will replace class name in the bytecode of
 33  * template class. It can be used for example to detect memory leaks in class
 34  * loading or to quickly fill Metaspace.
 35  */
 36 class TemplateClass {
 37 }
 38 
 39 public class GeneratingClassLoader extends ClassLoader {
 40 
 41     public synchronized Class<?> loadClass(String name) throws ClassNotFoundException {
 42         return loadClass(name, false);
 43     }
 44 
 45     public synchronized Class<?> loadClass(String name, boolean resolve)
 46             throws ClassNotFoundException {
 47         Class<?> c = findLoadedClass(name);
 48         if (c != null) {
 49             return c;
 50         }
 51         if (!name.startsWith(PREFIX)) {
 52             return super.loadClass(name, resolve);
 53         }
 54         if (name.length() != templateClassName.length()) {
 55             throw new ClassNotFoundException("Only can load classes with name.length() = " + getNameLength() + " got: '" + name + "' length: " + name.length());
 56         }
 57         byte[] bytecode = getPatchedByteCode(name);
 58         c = defineClass(name, bytecode, 0, bytecode.length);
 59         if (resolve) {
 60             resolveClass(c);
 61         }
 62         return c;
 63     }
 64 
 65     /**
 66      * Create generating class loader that will use class file for given class
 67      * from classpath as template.
 68      */
 69     @SuppressWarnings("initialization")
 70     public GeneratingClassLoader(String templateClassName) {
 71         this.templateClassName = templateClassName;
 72         classPath = System.getProperty("java.class.path").split(File.pathSeparator);
 73         try {
 74             templateClassNameBytes = templateClassName.getBytes(encoding);
 75         } catch (UnsupportedEncodingException e) {
 76             throw new RuntimeException(e);
 77         }
 78     }
 79 
 80     /**
 81      * Create generating class loader that will use class file for
 82      * nsk.share.classload.TemplateClass as template.
 83      */
 84     public GeneratingClassLoader() {
 85         this(TemplateClass.class.getName());
 86     }
 87 
 88     public int getNameLength() {
 89         return templateClassName.length();
 90     }
 91 
 92     String getPrefix() {
 93         return PREFIX;
 94     }
 95 
 96     public String getClassName(int number) {
 97         StringBuffer sb = new StringBuffer();
 98         sb.append(PREFIX);
 99         sb.append(number);
100         int n = templateClassName.length() - sb.length();
101         for (int i = 0; i < n; ++i) {
102             sb.append("_");
103         }
104         return sb.toString();
105     }
106 
107     private byte[] getPatchedByteCode(String name) throws ClassNotFoundException {
108         try {
109             byte[] bytecode = getByteCode();
110             String fname = name.replace(".", File.separator);
111             byte[] replaceBytes = fname.getBytes(encoding);
112             for (int offset : offsets) {
113                 for (int i = 0; i < replaceBytes.length; ++i) {
114                     bytecode[offset + i] = replaceBytes[i];
115                 }
116             }
117             return bytecode;
118         } catch (UnsupportedEncodingException e) {
119             throw new RuntimeException(e);
120         }
121     }
122 
123     private byte[] getByteCode() throws ClassNotFoundException {
124         if (bytecode == null) {
125             readByteCode();
126         }
127         if (offsets == null) {
128             getOffsets(bytecode);
129             if (offsets == null) {
130                 throw new RuntimeException("Class name not found in template class file");
131             }
132         }
133         return bytecode.clone();
134     }
135 
136     private void readByteCode() throws ClassNotFoundException {
137         String fname = templateClassName.replace(".", File.separator) + ".class";
138         File target = null;
139         for (int i = 0; i < classPath.length; ++i) {
140             target = new File(classPath[i] + File.separator + fname);
141             if (target.exists()) {
142                 break;
143             }
144         }
145 
146         if (target == null || !target.exists()) {
147             throw new ClassNotFoundException("File not found: " + target);
148         }
149         try {
150             bytecode = ClassLoadUtils.readFile(target);
151         } catch (IOException e) {
152             throw new ClassNotFoundException(templateClassName, e);
153         }
154     }
155 
156     private void getOffsets(byte[] bytecode) {
157         List<Integer> offsets = new ArrayList<Integer>();
158         if (this.offsets == null) {
159             String pname = templateClassName.replace(".", "/");
160             try {
161                 byte[] pnameb = pname.getBytes(encoding);
162                 int i = 0;
163                 while (true) {
164                     while (i < bytecode.length) {
165                         int j = 0;
166                         while (j < pnameb.length && bytecode[i + j] == pnameb[j]) {
167                             ++j;
168                         }
169                         if (j == pnameb.length) {
170                             break;
171                         }
172                         i++;
173                     }
174                     if (i == bytecode.length) {
175                         break;
176                     }
177                     offsets.add(i);
178                     i++;
179                 }
180             } catch (UnsupportedEncodingException e) {
181                 throw new RuntimeException(e);
182             }
183             this.offsets = new int[offsets.size()];
184             for (int i = 0; i < offsets.size(); ++i) {
185                 this.offsets[i] = offsets.get(i).intValue();
186             }
187         }
188     }
189 
190     public static final String DEFAULT_CLASSNAME = TemplateClass.class.getName();
191     static final String PREFIX = "Class";
192 
193     private final String[] classPath;
194     private byte[] bytecode;
195     private int[] offsets;
196     private final String encoding = "UTF8";
197     private final String templateClassName;
198     private final byte[] templateClassNameBytes;
199 }