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