1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 package com.sun.star.comp.sdbc.classloading;
22 
23 import java.lang.ref.WeakReference;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.net.URLClassLoader;
27 import java.util.ArrayList;
28 import java.util.Iterator;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.StringTokenizer;
32 
33 import org.apache.openoffice.comp.sdbc.dbtools.comphelper.CompHelper;
34 
35 import com.sun.star.lang.IllegalArgumentException;
36 import com.sun.star.uno.RuntimeException;
37 import com.sun.star.uno.UnoRuntime;
38 import com.sun.star.uno.XComponentContext;
39 import com.sun.star.uri.UriReferenceFactory;
40 import com.sun.star.uri.XUriReferenceFactory;
41 import com.sun.star.uri.XVndSunStarExpandUrlReference;
42 import com.sun.star.util.XMacroExpander;
43 
44 /**
45  * A map from pairs of (classPath, name) to pairs of weak Java
46  * references to (ClassLoader, Class) is maintained, so that a class is only
47  * loaded once.
48  *
49  * It may happen that the weak reference to the ClassLoader becomes null while
50  * the reference to the Class remains non-null (in case the Class was actually
51  * loaded by some parent of the ClassLoader), in which case the ClassLoader is
52  * resurrected (which cannot cause any classes to be loaded multiple times, as
53  * the ClassLoader is no longer reachable, so no classes it has ever loaded are
54  * still reachable).
55  *
56  * Similarly, it may happen that the weak reference to the Class becomes null
57  * while the reference to the ClassLoader remains non-null, in which case the
58  * Class is simply re-loaded.
59  */
60 public class ClassMap {
61 
62     private static class ClassMapEntry {
63         String classPath;
64         String className;
65         WeakReference<ClassLoader> classLoader;
66         WeakReference<Class<?>> classObject;
67     }
68 
69     private final LinkedList<ClassMapEntry> map = new LinkedList<>();
70 
loadClass(XComponentContext context, String classPath, String className)71     public synchronized ClassLoaderAndClass loadClass(XComponentContext context, String classPath, String className)
72             throws MalformedURLException, ClassNotFoundException {
73 
74         ClassLoader classLoader = null;
75         Class<?> classObject = null;
76         // Prune dangling weak references from the list while searching for a match,
77         // so that the list cannot grow unbounded:
78         ClassMapEntry matchingEntry = null;
79         for (Iterator<ClassMapEntry> it = map.iterator(); it.hasNext();) {
80             ClassMapEntry entry = it.next();
81             classLoader = entry.classLoader.get();
82             classObject = entry.classObject.get();
83             if (classLoader == null && classObject == null) {
84                 it.remove();
85             } else if (entry.classPath.equals(classPath) && entry.className.equals(className)) {
86                 matchingEntry = entry;
87                 break;
88             }
89         }
90         if (classLoader == null || classObject == null) {
91             if (matchingEntry == null) {
92                 // Push a new ClassMapEntry (which can potentially fail) before
93                 // loading the class, so that it never happens that a class is
94                 // loaded but not added to the map (which could have effects on the
95                 // JVM that are not easily undone).  If the pushed ClassMapEntry is
96                 // not used after all (return false, etc.) it will be pruned on next
97                 // call because its classLoader/classObject are null:
98                 matchingEntry = new ClassMapEntry();
99                 matchingEntry.classPath = classPath;
100                 matchingEntry.className = className;
101                 map.addFirst(matchingEntry);
102             }
103             if (classLoader == null) {
104                 List<URL> urls = translateToUrls(context, classPath);
105                 classLoader = new URLClassLoader(urls.toArray(new URL[0]));
106             }
107             if (classObject == null) {
108                 classObject = classLoader.loadClass(className);
109             }
110             matchingEntry.classLoader = new WeakReference<ClassLoader>(classLoader);
111             matchingEntry.classObject = new WeakReference<Class<?>>(classObject);
112         }
113         return new ClassLoaderAndClass(classLoader, classObject);
114     }
115 
translateToUrls(XComponentContext context, String classPath)116     private static List<URL> translateToUrls(XComponentContext context, String classPath) throws MalformedURLException {
117         StringTokenizer tokenizer = new StringTokenizer(classPath, " ", false);
118         ArrayList<URL> urls = new ArrayList<>();
119         while (tokenizer.hasMoreTokens()) {
120             String url = tokenizer.nextToken();
121             XUriReferenceFactory uriReferenceFactory = null;
122             XVndSunStarExpandUrlReference expUrl = null;
123             XMacroExpander macroExpander = null;
124             try {
125                 uriReferenceFactory = UriReferenceFactory.create(context);
126                 expUrl = UnoRuntime.queryInterface(
127                         XVndSunStarExpandUrlReference.class, uriReferenceFactory.parse(url));
128                 if (expUrl != null) {
129                     macroExpander = UnoRuntime.queryInterface(
130                             XMacroExpander.class,
131                             context.getValueByName("/singletons/com.sun.star.util.theMacroExpander"));
132                     try {
133                         url = expUrl.expand(macroExpander);
134                     } catch (IllegalArgumentException illegalArgumentException) {
135                         throw new RuntimeException(
136                                 "com.sun.star.lang.IllegalArgumentException: " + illegalArgumentException.getMessage(),
137                                 illegalArgumentException);
138                     }
139                 }
140             } finally {
141                 CompHelper.disposeComponent(uriReferenceFactory);
142                 CompHelper.disposeComponent(expUrl);
143                 CompHelper.disposeComponent(macroExpander);
144             }
145             URL javaURL = new URL(url);
146             urls.add(javaURL);
147         }
148         return urls;
149     }
150 }
151