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