1/*
2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20package com.sun.org.apache.bcel.internal.util;
21
22import java.io.IOException;
23import java.io.InputStream;
24
25import com.sun.org.apache.bcel.internal.classfile.ClassParser;
26import com.sun.org.apache.bcel.internal.classfile.JavaClass;
27import java.lang.ref.SoftReference;
28import java.util.HashMap;
29import java.util.Map;
30
31/**
32 * This repository is used in situations where a Class is created outside the
33 * realm of a ClassLoader. Classes are loaded from the file systems using the
34 * paths specified in the given class path. By default, this is the value
35 * returned by ClassPath.getClassPath(). <br>
36 * This repository uses a factory design, allowing it to maintain a collection
37 * of different classpaths, and as such It is designed to be used as a singleton
38 * per classpath.
39 *
40 * @see com.sun.org.apache.bcel.internal.Repository
41 *
42 * @version $Id: SyntheticRepository.java 1748124 2016-06-13 08:02:16Z ggregory
43 * $
44 */
45public class SyntheticRepository implements Repository {
46
47    // CLASSNAME X JAVACLASS
48    private final Map<String, SoftReference<JavaClass>> loadedClasses = new HashMap<>();
49
50    private SyntheticRepository() {
51    }
52
53    public static SyntheticRepository getInstance() {
54        return new SyntheticRepository();
55    }
56
57    /**
58     * Store a new JavaClass instance into this Repository.
59     */
60    @Override
61    public void storeClass(final JavaClass clazz) {
62        loadedClasses.put(clazz.getClassName(), new SoftReference<>(clazz));
63        clazz.setRepository(this);
64    }
65
66    /**
67     * Remove class from repository
68     */
69    @Override
70    public void removeClass(final JavaClass clazz) {
71        loadedClasses.remove(clazz.getClassName());
72    }
73
74    /**
75     * Find an already defined (cached) JavaClass object by name.
76     */
77    @Override
78    public JavaClass findClass(final String className) {
79        final SoftReference<JavaClass> ref = loadedClasses.get(className);
80        if (ref == null) {
81            return null;
82        }
83        return ref.get();
84    }
85
86    /**
87     * Finds a JavaClass object by name. If it is already in this Repository, the
88     * Repository version is returned.
89     *
90     * @param className the name of the class
91     * @return the JavaClass object
92     * @throws ClassNotFoundException if the class is not in the Repository
93     */
94    @Override
95    public JavaClass loadClass(String className) throws ClassNotFoundException {
96        if ((className == null) || className.isEmpty()) {
97            throw new IllegalArgumentException("Invalid class name " + className);
98        }
99        className = className.replace('/', '.'); // Just in case, canonical form
100        final JavaClass clazz = findClass(className);
101        if (clazz != null) {
102            return clazz;
103        }
104
105        IOException e = new IOException("Couldn't find: " + className + ".class");
106        throw new ClassNotFoundException("Exception while looking for class " +
107                className + ": " + e, e);
108    }
109
110    /**
111     * Find the JavaClass object for a runtime Class object. If a class with the
112     * same name is already in this Repository, the Repository version is
113     * returned. Otherwise, getResourceAsStream() is called on the Class object
114     * to find the class's representation. If the representation is found, it is
115     * added to the Repository.
116     *
117     * @see Class
118     * @param clazz the runtime Class object
119     * @return JavaClass object for given runtime class
120     * @throws ClassNotFoundException if the class is not in the Repository, and
121     * its representation could not be found
122     */
123    @Override
124    public JavaClass loadClass(final Class<?> clazz) throws ClassNotFoundException {
125        final String className = clazz.getName();
126        final JavaClass repositoryClass = findClass(className);
127        if (repositoryClass != null) {
128            return repositoryClass;
129        }
130        String name = className;
131        final int i = name.lastIndexOf('.');
132        if (i > 0) {
133            name = name.substring(i + 1);
134        }
135        JavaClass cls = null;
136        try (InputStream clsStream = clazz.getResourceAsStream(name + ".class")) {
137            return cls = loadClass(clsStream, className);
138        } catch (final IOException e) {
139            return cls;
140        }
141
142    }
143
144
145    private JavaClass loadClass(final InputStream is, final String className)
146            throws ClassNotFoundException {
147        try {
148            if (is != null) {
149                final ClassParser parser = new ClassParser(is, className);
150                final JavaClass clazz = parser.parse();
151                storeClass(clazz);
152                return clazz;
153            }
154        } catch (final IOException e) {
155            throw new ClassNotFoundException("Exception while looking for class "
156                    + className + ": " + e, e);
157        } finally {
158            if (is != null) {
159                try {
160                    is.close();
161                } catch (final IOException e) {
162                    // ignored
163                }
164            }
165        }
166        throw new ClassNotFoundException("SyntheticRepository could not load "
167                + className);
168    }
169
170    /**
171     * Clear all entries from cache.
172     */
173    @Override
174    public void clear() {
175        loadedClasses.clear();
176    }
177}
178