1/*
2 * Copyright (c) 2015, 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 */
23package utils;
24
25import java.io.*;
26import 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 PermGen.
35 */
36class GeneratingClassLoader extends ClassLoader {
37
38    public synchronized Class loadClass(String name) throws ClassNotFoundException {
39        return loadClass(name, false);
40    }
41
42    public synchronized Class loadClass(String name, boolean resolve)
43            throws ClassNotFoundException {
44        Class c = findLoadedClass(name);
45        if (c != null) {
46            return c;
47        }
48        if (!name.startsWith(PREFIX)) {
49            return super.loadClass(name, resolve);
50        }
51        if (name.length() != templateClassName.length()) {
52            throw new ClassNotFoundException("Only can load classes with name.length() = " + getNameLength() + " got: '" + name + "' length: " + name.length());
53        }
54        byte[] bytecode = getPatchedByteCode(name);
55        c = defineClass(name, bytecode, 0, bytecode.length);
56        if (resolve) {
57            resolveClass(c);
58        }
59        return c;
60    }
61
62    /**
63     * Create generating class loader that will use class file for given class
64     * from classpath as template.
65     */
66    GeneratingClassLoader(String templateClassName) {
67        this.templateClassName = templateClassName;
68        classPath = System.getProperty("java.class.path").split(File.pathSeparator);
69        try {
70            templateClassNameBytes = templateClassName.getBytes(encoding);
71        } catch (UnsupportedEncodingException e) {
72            throw new RuntimeException(e);
73        }
74    }
75
76    /**
77     * Create generating class loader that will use class file for
78     * nsk.share.classload.TemplateClass as template.
79     */
80    GeneratingClassLoader() {
81        this(TemplateClass.class.getName());
82    }
83
84    int getNameLength() {
85        return templateClassName.length();
86    }
87
88    String getPrefix() {
89        return PREFIX;
90    }
91
92    String getClassName(int number) {
93        StringBuffer sb = new StringBuffer();
94        sb.append(PREFIX);
95        sb.append(number);
96        int n = templateClassName.length() - sb.length();
97        for (int i = 0; i < n; ++i) {
98            sb.append("_");
99        }
100        return sb.toString();
101    }
102
103    private byte[] getPatchedByteCode(String name) throws ClassNotFoundException {
104        try {
105            byte[] bytecode = getByteCode();
106            String fname = name.replace(".", File.separator);
107            byte[] replaceBytes = fname.getBytes(encoding);
108            for (int offset : offsets) {
109                for (int i = 0; i < replaceBytes.length; ++i) {
110                    bytecode[offset + i] = replaceBytes[i];
111                }
112            }
113            return bytecode;
114        } catch (UnsupportedEncodingException e) {
115            throw new RuntimeException(e);
116        }
117    }
118
119    private byte[] getByteCode() throws ClassNotFoundException {
120        if (bytecode == null) {
121            readByteCode();
122        }
123        if (offsets == null) {
124            getOffsets(bytecode);
125            if (offsets == null) {
126                throw new RuntimeException("Class name not found in template class file");
127            }
128        }
129        return (byte[]) bytecode.clone();
130    }
131
132    private void readByteCode() throws ClassNotFoundException {
133        String fname = templateClassName.replace(".", File.separator) + ".class";
134        File target = null;
135        for (int i = 0; i < classPath.length; ++i) {
136            target = new File(classPath[i] + File.separator + fname);
137            if (target.exists()) {
138                break;
139            }
140        }
141
142        if (target == null || !target.exists()) {
143            throw new ClassNotFoundException("File not found: " + target);
144        }
145        try {
146            bytecode = ClassLoadUtils.readFile(target);
147        } catch (IOException e) {
148            throw new ClassNotFoundException(templateClassName, e);
149        }
150    }
151
152    private void getOffsets(byte[] bytecode) {
153        List<Integer> offsets = new ArrayList<Integer>();
154        if (this.offsets == null) {
155            String pname = templateClassName.replace(".", "/");
156            try {
157                byte[] pnameb = pname.getBytes(encoding);
158                int i = 0;
159                while (true) {
160                    while (i < bytecode.length) {
161                        int j = 0;
162                        while (j < pnameb.length && bytecode[i + j] == pnameb[j]) {
163                            ++j;
164                        }
165                        if (j == pnameb.length) {
166                            break;
167                        }
168                        i++;
169                    }
170                    if (i == bytecode.length) {
171                        break;
172                    }
173                    offsets.add(new Integer(i));
174                    i++;
175                }
176            } catch (UnsupportedEncodingException e) {
177                throw new RuntimeException(e);
178            }
179            this.offsets = new int[offsets.size()];
180            for (int i = 0; i < offsets.size(); ++i) {
181                this.offsets[i] = offsets.get(i).intValue();
182            }
183        }
184    }
185
186    static final String DEFAULT_CLASSNAME = TemplateClass.class.getName();
187    static final String PREFIX = "Class";
188
189    private final String[] classPath;
190    private byte[] bytecode;
191    private int[] offsets;
192    private final String encoding = "UTF8";
193    private final String templateClassName;
194    private final byte[] templateClassNameBytes;
195}
196