1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.resources.manager; 18 19 import com.android.ide.eclipse.adt.AdtConstants; 20 import com.android.ide.eclipse.adt.internal.build.BuildHelper; 21 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 22 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 23 24 import org.eclipse.core.resources.IProject; 25 import org.eclipse.core.resources.IResource; 26 import org.eclipse.core.resources.IWorkspaceRoot; 27 import org.eclipse.core.resources.ResourcesPlugin; 28 import org.eclipse.core.runtime.CoreException; 29 import org.eclipse.core.runtime.IPath; 30 import org.eclipse.jdt.core.IClasspathEntry; 31 import org.eclipse.jdt.core.IJavaProject; 32 import org.eclipse.jdt.core.JavaCore; 33 34 import java.io.File; 35 import java.io.FileInputStream; 36 import java.io.FileNotFoundException; 37 import java.io.IOException; 38 import java.net.MalformedURLException; 39 import java.net.URL; 40 import java.net.URLClassLoader; 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * ClassLoader able to load class from output of an Eclipse project. 46 */ 47 public final class ProjectClassLoader extends ClassLoader { 48 49 private final IJavaProject mJavaProject; 50 private URLClassLoader mJarClassLoader; 51 private boolean mInsideJarClassLoader = false; 52 53 public ProjectClassLoader(ClassLoader parentClassLoader, IProject project) { 54 super(parentClassLoader); 55 mJavaProject = JavaCore.create(project); 56 } 57 58 @Override 59 protected Class<?> findClass(String name) throws ClassNotFoundException { 60 // if we are here through a child classloader, throw an exception. 61 if (mInsideJarClassLoader) { 62 throw new ClassNotFoundException(name); 63 } 64 65 // attempt to load the class from the main project 66 Class<?> clazz = loadFromProject(mJavaProject, name); 67 68 if (clazz != null) { 69 return clazz; 70 } 71 72 // attempt to load the class from the jar dependencies 73 clazz = loadClassFromJar(name); 74 if (clazz != null) { 75 return clazz; 76 } 77 78 // attempt to load the class from the libraries 79 try { 80 // get the project info 81 ProjectState projectState = Sdk.getProjectState(mJavaProject.getProject()); 82 83 // this can happen if the project has no project.properties. 84 if (projectState != null) { 85 86 List<IProject> libProjects = projectState.getFullLibraryProjects(); 87 List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects( 88 libProjects); 89 90 for (IJavaProject javaProject : referencedJavaProjects) { 91 clazz = loadFromProject(javaProject, name); 92 93 if (clazz != null) { 94 return clazz; 95 } 96 } 97 } 98 } catch (CoreException e) { 99 // log exception? 100 } 101 102 throw new ClassNotFoundException(name); 103 } 104 105 /** 106 * Attempts to load a class from a project output folder. 107 * @param project the project to load the class from 108 * @param name the name of the class 109 * @return a class object if found, null otherwise. 110 */ 111 private Class<?> loadFromProject(IJavaProject project, String name) { 112 try { 113 // get the project output folder. 114 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 115 IPath outputLocation = project.getOutputLocation(); 116 IResource outRes = root.findMember(outputLocation); 117 if (outRes == null) { 118 return null; 119 } 120 121 File outFolder = new File(outRes.getLocation().toOSString()); 122 123 // get the class name segments 124 String[] segments = name.split("\\."); //$NON-NLS-1$ 125 126 // try to load the class from the bin folder of the project. 127 File classFile = getFile(outFolder, segments, 0); 128 if (classFile == null) { 129 return null; 130 } 131 132 // load the content of the file and create the class. 133 FileInputStream fis = new FileInputStream(classFile); 134 byte[] data = new byte[(int)classFile.length()]; 135 int read = 0; 136 try { 137 read = fis.read(data); 138 } catch (IOException e) { 139 data = null; 140 } 141 fis.close(); 142 143 if (data != null) { 144 Class<?> clazz = defineClass(null, data, 0, read); 145 if (clazz != null) { 146 return clazz; 147 } 148 } 149 } catch (Exception e) { 150 // log the exception? 151 } 152 153 return null; 154 } 155 156 /** 157 * Returns the File matching the a certain path from a root {@link File}. 158 * <p/>The methods checks that the file ends in .class even though the last segment 159 * does not. 160 * @param parent the root of the file. 161 * @param segments the segments containing the path of the file 162 * @param index the offset at which to start looking into segments. 163 * @throws FileNotFoundException 164 */ 165 private File getFile(File parent, String[] segments, int index) 166 throws FileNotFoundException { 167 // reached the end with no match? 168 if (index == segments.length) { 169 throw new FileNotFoundException(); 170 } 171 172 String toMatch = segments[index]; 173 File[] files = parent.listFiles(); 174 175 // we're at the last segments. we look for a matching <file>.class 176 if (index == segments.length - 1) { 177 toMatch = toMatch + ".class"; 178 179 if (files != null) { 180 for (File file : files) { 181 if (file.isFile() && file.getName().equals(toMatch)) { 182 return file; 183 } 184 } 185 } 186 187 // no match? abort. 188 throw new FileNotFoundException(); 189 } 190 191 String innerClassName = null; 192 193 if (files != null) { 194 for (File file : files) { 195 if (file.isDirectory()) { 196 if (toMatch.equals(file.getName())) { 197 return getFile(file, segments, index+1); 198 } 199 } else if (file.getName().startsWith(toMatch)) { 200 if (innerClassName == null) { 201 StringBuilder sb = new StringBuilder(segments[index]); 202 for (int i = index + 1 ; i < segments.length ; i++) { 203 sb.append('$'); 204 sb.append(segments[i]); 205 } 206 sb.append(".class"); 207 208 innerClassName = sb.toString(); 209 } 210 211 if (file.getName().equals(innerClassName)) { 212 return file; 213 } 214 } 215 } 216 } 217 218 return null; 219 } 220 221 /** 222 * Loads a class from the 3rd party jar present in the project 223 * 224 * @return the class loader or null if not found. 225 */ 226 private Class<?> loadClassFromJar(String name) { 227 if (mJarClassLoader == null) { 228 // get the OS path to all the external jars 229 URL[] jars = getExternalJars(); 230 231 mJarClassLoader = new URLClassLoader(jars, this /* parent */); 232 } 233 234 try { 235 // because a class loader always look in its parent loader first, we need to know 236 // that we are querying the jar classloader. This will let us know to not query 237 // it again for classes we don't find, or this would create an infinite loop. 238 mInsideJarClassLoader = true; 239 return mJarClassLoader.loadClass(name); 240 } catch (ClassNotFoundException e) { 241 // not found? return null. 242 return null; 243 } finally { 244 mInsideJarClassLoader = false; 245 } 246 } 247 248 /** 249 * Returns an array of external jar files used by the project. 250 * @return an array of OS-specific absolute file paths 251 */ 252 private final URL[] getExternalJars() { 253 // get a java project from it 254 IJavaProject javaProject = JavaCore.create(mJavaProject.getProject()); 255 256 IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); 257 258 ArrayList<URL> oslibraryList = new ArrayList<URL>(); 259 IClasspathEntry[] classpaths = javaProject.readRawClasspath(); 260 if (classpaths != null) { 261 for (IClasspathEntry e : classpaths) { 262 if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY || 263 e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { 264 // if this is a classpath variable reference, we resolve it. 265 if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { 266 e = JavaCore.getResolvedClasspathEntry(e); 267 } 268 269 // get the IPath 270 IPath path = e.getPath(); 271 272 // check the name ends with .jar 273 if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { 274 boolean local = false; 275 IResource resource = wsRoot.findMember(path); 276 if (resource != null && resource.exists() && 277 resource.getType() == IResource.FILE) { 278 local = true; 279 try { 280 oslibraryList.add(new File(resource.getLocation().toOSString()) 281 .toURI().toURL()); 282 } catch (MalformedURLException mue) { 283 // pass 284 } 285 } 286 287 if (local == false) { 288 // if the jar path doesn't match a workspace resource, 289 // then we get an OSString and check if this links to a valid file. 290 String osFullPath = path.toOSString(); 291 292 File f = new File(osFullPath); 293 if (f.exists()) { 294 try { 295 oslibraryList.add(f.toURI().toURL()); 296 } catch (MalformedURLException mue) { 297 // pass 298 } 299 } 300 } 301 } 302 } 303 } 304 } 305 306 return oslibraryList.toArray(new URL[oslibraryList.size()]); 307 } 308 } 309