aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/javassist/Loader.java
blob: 37f83d686f3665cc795067408f95e668aa95ea5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package javassist;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Vector;

import javassist.bytecode.ClassFile;

/**
 * The class loader for Javassist.
 *
 * <p>This is a sample class loader using <code>ClassPool</code>.
 * Unlike a regular class loader, this class loader obtains bytecode
 * from a <code>ClassPool</code>.
 *
 * <p>Note that Javassist can be used without this class loader; programmers
 * can define their own versions of class loader.  They can run
 * a program even without any user-defined class loader if that program
 * is statically translated with Javassist.
 * This class loader is just provided as a utility class.
 *
 * <p>Suppose that an instance of <code>MyTranslator</code> implementing
 * the interface <code>Translator</code> is responsible for modifying
 * class files.
 * The startup program of an application using <code>MyTranslator</code>
 * should be something like this:
 *
 * <pre>
 * import javassist.*;
 *
 * public class Main {
 *   public static void main(String[] args) throws Throwable {
 *     MyTranslator myTrans = new MyTranslator();
 *     ClassPool cp = ClassPool.getDefault();
 *     Loader cl = new Loader(cp);
 *     cl.addTranslator(cp, myTrans);
 *     cl.run("MyApp", args);
 *   }
 * }
 * </pre>
 *
 * <p>Class <code>MyApp</code> is the main program of the application.
 *
 * <p>This program should be executed as follows:
 *
 * <pre>
 * % java Main <i>arg1</i> <i>arg2</i>...
 * </pre>
 *
 * <p>It modifies the class <code>MyApp</code> with a <code>MyTranslator</code>
 * object before the JVM loads it.
 * Then it calls <code>main()</code> in <code>MyApp</code> with arguments
 * <i>arg1</i>, <i>arg2</i>, ...
 *
 * <p>This program execution is equivalent to:
 *
 * <pre>
 * % java MyApp <i>arg1</i> <i>arg2</i>...
 * </pre>
 *
 * <p>except that classes are translated by <code>MyTranslator</code>
 * at load time.
 *
 * <p>If only a particular class must be modified when it is loaded,
 * the startup program can be simpler; <code>MyTranslator</code> is
 * unnecessary.  For example, if only a class <code>test.Rectangle</code>
 * is modified, the <code>main()</code> method above will be the following:
 *
 * <pre>
 * ClassPool cp = ClassPool.getDefault();
 * Loader cl = new Loader(cp);
 * CtClass ct = cp.get("test.Rectangle");
 * ct.setSuperclass(cp.get("test.Point"));
 * cl.run("MyApp", args);</pre>
 *
 * <p>This program changes the super class of the <code>test.Rectangle</code>
 * class.
 *
 * <p><b>Note 1:</b>
 *
 * <p>This class loader does not allow the users to intercept the loading
 * of <code>java.*</code> and <code>javax.*</code> classes (and
 * <code>sun.*</code>, <code>org.xml.*</code>, ...) unless
 * <code>Loader.doDelegation</code> is <code>false</code>.  This is because
 * the JVM prohibits a user class loader from loading a system class.
 * Also see Note 2.
 * If this behavior is not appropriate, a subclass of <code>Loader</code>
 * must be defined and <code>loadClassByDelegation()</code> must be overridden.
 *
 * <p><b>Note 2:</b>
 *
 * <p>If classes are loaded with different class loaders, they belong to
 * separate name spaces.  If class <code>C</code> is loaded by a class
 * loader <code>CL</code>, all classes that the class <code>C</code>
 * refers to are also loaded by <code>CL</code>.  However, if <code>CL</code>
 * delegates the loading of the class <code>C</code> to <code>CL'</code>,
 * then those classes that the class <code>C</code> refers to
 * are loaded by a parent class loader <code>CL'</code>
 * instead of <code>CL</code>.
 *
 * <p>If an object of class <code>C</code> is assigned
 * to a variable of class <code>C</code> belonging to a different name
 * space, then a <code>ClassCastException</code> is thrown.
 *
 * <p>Because of the fact above, this loader delegates only the loading of
 * <code>javassist.Loader</code>
 * and classes included in package <code>java.*</code> and
 * <code>javax.*</code> to the parent class
 * loader.  Other classes are directly loaded by this loader.
 *
 * <p>For example, suppose that <code>java.lang.String</code> would be loaded
 * by this loader while <code>java.io.File</code> is loaded by the parent
 * class loader.  If the constructor of <code>java.io.File</code> is called
 * with an instance of <code>java.lang.String</code>, then it may throw
 * an exception since it accepts an instance of only the
 * <code>java.lang.String</code> loaded by the parent class loader.
 *
 * @see javassist.ClassPool
 * @see javassist.Translator
 */
public class Loader extends ClassLoader {

    /**
     * A simpler class loader.
     * This is a class loader that exposes the protected {@code defineClass()} method
     * declared in {@code java.lang.ClassLoader}.  It provides a method similar to
     * {@code CtClass#toClass()}.
     *
     * <p>When loading a class, this class loader delegates the work to the
     * parent class loader unless the loaded classes are explicitly given
     * by {@link #invokeDefineClass(CtClass)}.
     * Note that a class {@code Foo} loaded by this class loader is
     * different from the class with the same name {@code Foo} but loaded by
     * another class loader.  This is Java's naming rule.
     * </p>
     *
     * @since 3.24
     */
    public static class Simple extends ClassLoader {
        /**
         * Constructs a class loader.
         */
        public Simple() {}

        /**
         * Constructs a class loader.
         * @param parent    the parent class loader.
         */
        public Simple(ClassLoader parent) {
            super(parent);
        }

        /**
         * Invokes the protected {@code defineClass()} in {@code ClassLoader}.
         * It converts the given {@link CtClass} object into a {@code java.lang.Class} object.
         */
        public Class<?> invokeDefineClass(CtClass cc) throws IOException, CannotCompileException {
            byte[] code = cc.toBytecode();
            return defineClass(cc.getName(), code, 0, code.length);
        }
    }

    private HashMap<String,ClassLoader> notDefinedHere; // must be atomic.
    private Vector<String> notDefinedPackages; // must be atomic.
    private ClassPool source;
    private Translator translator;
    private ProtectionDomain domain; 

    /**
     * Specifies the algorithm of class loading.
     *
     * <p>This class loader uses the parent class loader for
     * <code>java.*</code> and <code>javax.*</code> classes.
     * If this variable <code>doDelegation</code>
     * is <code>false</code>, this class loader does not delegate those
     * classes to the parent class loader.
     *
     * <p>The default value is <code>true</code>.
     */
    public boolean doDelegation = true;

    /**
     * Creates a new class loader.
     */
    public Loader() {
        this(null);
    }

    /**
     * Creates a new class loader.
     *
     * @param cp        the source of class files.
     */
    public Loader(ClassPool cp) {
        init(cp);
    }

    /**
     * Creates a new class loader
     * using the specified parent class loader for delegation.
     *
     * @param parent    the parent class loader.
     * @param cp        the source of class files.
     */
    public Loader(ClassLoader parent, ClassPool cp) {
        super(parent);
        init(cp);
    }

    private void init(ClassPool cp) {
        notDefinedHere = new HashMap<String,ClassLoader>();
        notDefinedPackages = new Vector<String>();
        source = cp;
        translator = null;
        domain = null;
        delegateLoadingOf("javassist.Loader");
    }

    /**
     * Records a class so that the loading of that class is delegated
     * to the parent class loader.
     *
     * <p>If the given class name ends with <code>.</code> (dot), then
     * that name is interpreted as a package name.  All the classes
     * in that package and the sub packages are delegated.
     */
    public void delegateLoadingOf(String classname) {
        if (classname.endsWith("."))
            notDefinedPackages.addElement(classname);
        else
            notDefinedHere.put(classname, this);
    }

    /**
     * Sets the protection domain for the classes handled by this class
     * loader.  Without registering an appropriate protection domain,
     * the program loaded by this loader will not work with a security
     * manager or a signed jar file.
     */
    public void setDomain(ProtectionDomain d) {
        domain = d;
    }

    /**
     * Sets the soruce <code>ClassPool</code>.
     */
    public void setClassPool(ClassPool cp) {
        source = cp;
    }

    /**
     * Adds a translator, which is called whenever a class is loaded.
     *
     * @param cp        the <code>ClassPool</code> object for obtaining
     *                  a class file.
     * @param t         a translator.
     * @throws NotFoundException        if <code>t.start()</code> throws an exception.
     * @throws CannotCompileException   if <code>t.start()</code> throws an exception.
     */
    public void addTranslator(ClassPool cp, Translator t)
        throws NotFoundException, CannotCompileException {
        source = cp;
        translator = t;
        t.start(cp);
    }

    /**
     * Loads a class with an instance of <code>Loader</code>
     * and calls <code>main()</code> of that class.
     *
     * <p>This method calls <code>run()</code>.
     *
     * @param args              command line parameters.
     * <br>&nbsp;&nbsp;{@code args[0]} is the class name to be loaded.
     * <br>&nbsp;&nbsp;{@code args[1..n]} are parameters passed
     *                      to the target {@code main()}.
     *
     * @see javassist.Loader#run(String[])
     */
    public static void main(String[] args) throws Throwable {
        Loader cl = new Loader();
        cl.run(args);
    }

    /**
     * Loads a class and calls <code>main()</code> in that class.
     *
     * @param args              command line parameters.
     *
     * <br>&nbsp;&nbsp;{@code args[0]} is the class name to be loaded.
     * <br>&nbsp;&nbsp;{@code args[1..n]} are parameters passed
     *                      to the target {@code main()}.
     */
    public void run(String[] args) throws Throwable {
        if (args.length >= 1)
            run(args[0], Arrays.copyOfRange(args, 1, args.length));
    }

    /**
     * Loads a class and calls <code>main()</code> in that class.
     *
     * @param classname         the loaded class.
     * @param args              parameters passed to <code>main()</code>.
     */
    public void run(String classname, String[] args) throws Throwable {
        Class<?> c = loadClass(classname);
        try {
            c.getDeclaredMethod("main", new Class<?>[] { String[].class }).invoke(
                null,
                new Object[] { args });
        }
        catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }

    /**
     * Requests the class loader to load a class.
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassFormatError, ClassNotFoundException {
        name = name.intern();
        synchronized (name) {
            Class<?> c = findLoadedClass(name);
            if (c == null)
                c = loadClassByDelegation(name);

            if (c == null)
                c = findClass(name);

            if (c == null)
                c = delegateToParent(name);

            if (resolve)
                resolveClass(c);

            return c;
        }
    }

    /**
     * Finds the specified class using <code>ClassPath</code>.
     * If the source throws an exception, this returns null.
     *
     * <p>This method can be overridden by a subclass of
     * <code>Loader</code>.  Note that the overridden method must not throw
     * an exception when it just fails to find a class file.
     *
     * @return      null if the specified class could not be found.
     * @throws ClassNotFoundException   if an exception is thrown while
     *                                  obtaining a class file.
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classfile;
        try {
            if (source != null) {
                if (translator != null)
                    translator.onLoad(source, name);

                try {
                    classfile = source.get(name).toBytecode();
                }
                catch (NotFoundException e) {
                    return null;
                }
            }
            else {
                String jarname = "/" + name.replace('.', '/') + ".class";
                InputStream in = this.getClass().getResourceAsStream(jarname);
                if (in == null)
                    return null;

                classfile = ClassPoolTail.readStream(in);
            }
        }
        catch (Exception e) {
            throw new ClassNotFoundException(
                "caught an exception while obtaining a class file for "
                + name, e);
        }

        int i = name.lastIndexOf('.');
        if (i != -1) {
            String pname = name.substring(0, i);
            if (isDefinedPackage(pname))
                try {
                    definePackage(
                        pname, null, null, null, null, null, null, null);
                }
                catch (IllegalArgumentException e) {
                    // ignore.  maybe the package object for the same
                    // name has been created just right away.
                }
        }

        if (domain == null)
            return defineClass(name, classfile, 0, classfile.length);
        return defineClass(name, classfile, 0, classfile.length, domain);
    }

    private boolean isDefinedPackage(String name) {
        if (ClassFile.MAJOR_VERSION >= ClassFile.JAVA_9)
            return getDefinedPackage(name) == null;
        else
            return getPackage(name) == null;
    }

    protected Class<?> loadClassByDelegation(String name)
        throws ClassNotFoundException
    {
        /* The swing components must be loaded by a system
         * class loader.
         * javax.swing.UIManager loads a (concrete) subclass
         * of LookAndFeel by a system class loader and cast
         * an instance of the class to LookAndFeel for
         * (maybe) a security reason.  To avoid failure of
         * type conversion, LookAndFeel must not be loaded
         * by this class loader.
         */

        Class<?> c = null;
        if (doDelegation)
            if (name.startsWith("java.")
                || name.startsWith("javax.")
                || name.startsWith("sun.")
                || name.startsWith("com.sun.")
                || name.startsWith("org.w3c.")
                || name.startsWith("org.xml.")
                || notDelegated(name))
                c = delegateToParent(name);

        return c;
    }

    private boolean notDelegated(String name) {
        if (notDefinedHere.containsKey(name))
            return true;

        for (String pack : notDefinedPackages)
            if (name.startsWith(pack))
                return true;

        return false;
    }

    protected Class<?> delegateToParent(String classname)
        throws ClassNotFoundException
    {
        ClassLoader cl = getParent();
        if (cl != null)
            return cl.loadClass(classname);
        return findSystemClass(classname);
    }
}