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;
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     public static class ClassLoaderAndClass {
62         private final ClassLoader classLoader;
63         private final Class<?> classObject;
64 
ClassLoaderAndClass(ClassLoader classLoader, Class<?> classObject)65         public ClassLoaderAndClass(ClassLoader classLoader, Class<?> classObject) {
66             this.classLoader = classLoader;
67             this.classObject = classObject;
68         }
69 
getClassLoader()70         public ClassLoader getClassLoader() {
71             return classLoader;
72         }
73 
getClassObject()74         public Class<?> getClassObject() {
75             return classObject;
76         }
77     }
78 
79     private static class ClassMapEntry {
80         String classPath;
81         String className;
82         WeakReference<ClassLoader> classLoader;
83         WeakReference<Class<?>> classObject;
84     }
85 
86     private final LinkedList<ClassMapEntry> map = new LinkedList<>();
87 
loadClass(XComponentContext context, String classPath, String className)88     public synchronized ClassLoaderAndClass loadClass(XComponentContext context, String classPath, String className)
89             throws MalformedURLException, ClassNotFoundException {
90 
91         ClassLoader classLoader = null;
92         Class<?> classObject = null;
93         // Prune dangling weak references from the list while searching for a match,
94         // so that the list cannot grow unbounded:
95         ClassMapEntry matchingEntry = null;
96         for (Iterator<ClassMapEntry> it = map.iterator(); it.hasNext();) {
97             ClassMapEntry entry = it.next();
98             classLoader = entry.classLoader.get();
99             classObject = entry.classObject.get();
100             if (classLoader == null && classObject == null) {
101                 it.remove();
102             } else if (entry.classPath.equals(classPath) && entry.className.equals(className)) {
103                 matchingEntry = entry;
104                 break;
105             }
106         }
107         if (classLoader == null || classObject == null) {
108             if (matchingEntry == null) {
109                 // Push a new ClassMapEntry (which can potentially fail) before
110                 // loading the class, so that it never happens that a class is
111                 // loaded but not added to the map (which could have effects on the
112                 // JVM that are not easily undone).  If the pushed ClassMapEntry is
113                 // not used after all (return false, etc.) it will be pruned on next
114                 // call because its classLoader/classObject are null:
115                 matchingEntry = new ClassMapEntry();
116                 matchingEntry.classPath = classPath;
117                 matchingEntry.className = className;
118                 map.addFirst(matchingEntry);
119             }
120             if (classLoader == null) {
121                 List<URL> urls = translateToUrls(context, classPath);
122                 classLoader = new URLClassLoader(urls.toArray(new URL[0]));
123             }
124             if (classObject == null) {
125                 classObject = classLoader.loadClass(className);
126             }
127             matchingEntry.classLoader = new WeakReference<ClassLoader>(classLoader);
128             matchingEntry.classObject = new WeakReference<Class<?>>(classObject);
129         }
130         return new ClassLoaderAndClass(classLoader, classObject);
131     }
132 
translateToUrls(XComponentContext context, String classPath)133     private static List<URL> translateToUrls(XComponentContext context, String classPath) throws MalformedURLException {
134         StringTokenizer tokenizer = new StringTokenizer(classPath, " ", false);
135         ArrayList<URL> urls = new ArrayList<>();
136         while (tokenizer.hasMoreTokens()) {
137             String url = tokenizer.nextToken();
138             XUriReferenceFactory uriReferenceFactory = null;
139             XVndSunStarExpandUrlReference expUrl = null;
140             XMacroExpander macroExpander = null;
141             try {
142                 uriReferenceFactory = UriReferenceFactory.create(context);
143                 expUrl = UnoRuntime.queryInterface(
144                         XVndSunStarExpandUrlReference.class, uriReferenceFactory.parse(url));
145                 if (expUrl != null) {
146                     macroExpander = UnoRuntime.queryInterface(
147                             XMacroExpander.class,
148                             context.getValueByName("/singletons/com.sun.star.util.theMacroExpander"));
149                     try {
150                         url = expUrl.expand(macroExpander);
151                     } catch (IllegalArgumentException illegalArgumentException) {
152                         throw new RuntimeException(
153                                 "com.sun.star.lang.IllegalArgumentException: " + illegalArgumentException.getMessage(),
154                                 illegalArgumentException);
155                     }
156                 }
157             } finally {
158                 CompHelper.disposeComponent(uriReferenceFactory);
159                 CompHelper.disposeComponent(expUrl);
160                 CompHelper.disposeComponent(macroExpander);
161             }
162             URL javaURL = new URL(url);
163             urls.add(javaURL);
164         }
165         return urls;
166     }
167 }
168