001 /* 002 * Copyright (c) 2005 Stephen J. McConnell 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.lang; 020 021 import java.io.IOException; 022 import java.util.ArrayList; 023 import java.net.URL; 024 import java.net.URI; 025 import java.net.URLClassLoader; 026 027 import net.dpml.transit.Artifact; 028 029 import net.dpml.util.Logger; 030 031 /** 032 * A named classloader. 033 * 034 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 035 * @version 1.1.0 036 */ 037 public class StandardClassLoader extends URLClassLoader 038 { 039 //-------------------------------------------------------------------- 040 // static 041 //-------------------------------------------------------------------- 042 043 /** 044 * Internal utility class to build a classloader. If the supplied url 045 * sequence is zero length the parent classloader is returned directly. 046 * 047 * @param logger the logging channel 048 * @param name the name identifying the classloader 049 * @param category the category that this classloader is handling 050 * @param parent the parent classloader 051 * @param uris the uris to assign as classloader content 052 * @return the classloader 053 * @exception IOException if an I/O error occurs 054 */ 055 public static ClassLoader buildClassLoader( Logger logger, String name, Category category, ClassLoader parent, URI[] uris ) 056 throws IOException 057 { 058 URL[] urls = toURLs( uris ); 059 if( 0 == urls.length ) 060 { 061 return parent; 062 } 063 ArrayList list = new ArrayList(); 064 for( int i=0; i < urls.length; i++ ) 065 { 066 if( isaCandidate( parent, urls[i] ) ) 067 { 068 list.add( urls[i] ); 069 } 070 } 071 URL[] qualified = (URL[]) list.toArray( new URL[0] ); 072 if( qualified.length == 0 ) 073 { 074 return parent; 075 } 076 else 077 { 078 ClassLoader loader = 079 new StandardClassLoader( name, category, qualified, parent ); 080 classloaderConstructed( logger, name, category, loader ); 081 return loader; 082 } 083 } 084 085 /** 086 * Convert a sequence of URIs to URLs. 087 * @param uris the uris to convert 088 * @return the corresponding urls 089 * @exception IOException of a transformation error occurs 090 */ 091 public static URL[] toURLs( URI[] uris ) throws IOException 092 { 093 URL[] urls = new URL[ uris.length ]; 094 for( int i=0; i < urls.length; i++ ) 095 { 096 URI uri = uris[i]; 097 if( Artifact.isRecognized( uri ) ) 098 { 099 urls[i] = Artifact.toURL( uri ); 100 } 101 else 102 { 103 urls[i] = uri.toURL(); 104 } 105 } 106 return urls; 107 } 108 109 /** 110 * Test if the supplied url is already present within the supplied classloader. 111 * @param classloader the classloader to validate against 112 * @param url to url to check for 113 * @return true if the url is not included in the classloader 114 */ 115 private static boolean isaCandidate( ClassLoader classloader, URL url ) 116 { 117 if( classloader instanceof URLClassLoader ) 118 { 119 URL[] urls = ( (URLClassLoader) classloader ).getURLs(); 120 for( int i=0; i < urls.length; i++ ) 121 { 122 if( urls[i].equals( url ) ) 123 { 124 return false; 125 } 126 } 127 ClassLoader parent = classloader.getParent(); 128 if( parent == null ) 129 { 130 return true; 131 } 132 else 133 { 134 return isaCandidate( parent, url ); 135 } 136 } 137 else 138 { 139 return true; 140 } 141 } 142 143 //-------------------------------------------------------------------- 144 // state 145 //-------------------------------------------------------------------- 146 147 private final Category m_category; 148 private final String m_name; 149 150 //-------------------------------------------------------------------- 151 // constructor 152 //-------------------------------------------------------------------- 153 154 /** 155 * Creation of a new classloader. 156 * @param name a name identifying the plugin 157 * @param category the classloader category identifier 158 * @param urls an array of urls to add to the classloader 159 * @param parent the parent classloader 160 */ 161 public StandardClassLoader( String name, Category category, URL[] urls, ClassLoader parent ) 162 { 163 super( urls, parent ); 164 m_category = category; 165 m_name = name; 166 } 167 168 //-------------------------------------------------------------------- 169 // StandardClassLoader 170 //-------------------------------------------------------------------- 171 172 /** 173 * Return the classloader category 174 * @return the classloader category 175 */ 176 public Category getCategory() 177 { 178 return m_category; 179 } 180 181 /** 182 * Return a string representation of the classloader. 183 * @return the string value 184 */ 185 public String getAnnotations() 186 { 187 StringBuffer buffer = new StringBuffer(); 188 ClassLoader parent = getParent(); 189 if( parent instanceof URLClassLoader ) 190 { 191 URLClassLoader urlClassLoader = (URLClassLoader) parent; 192 buffer.append( getURLClassLoaderAnnotations( urlClassLoader ) ); 193 } 194 buffer.append( " " ); 195 URL[] urls = getURLs(); 196 for( int i=0; i<urls.length; i++ ) 197 { 198 String path = urls[i].toString(); 199 if( !path.startsWith( "file:" ) ) 200 { 201 buffer.append( path ); 202 buffer.append( " " ); 203 } 204 } 205 return buffer.toString().trim(); 206 } 207 208 private String getURLClassLoaderAnnotations( URLClassLoader classloader ) 209 { 210 StringBuffer buffer = new StringBuffer(); 211 ClassLoader parent = classloader.getParent(); 212 if( ( null != parent ) && ( parent instanceof URLClassLoader ) ) 213 { 214 URLClassLoader urlClassLoader = (URLClassLoader) parent; 215 buffer.append( getURLClassLoaderAnnotations( urlClassLoader ) ); 216 } 217 if( ClassLoader.getSystemClassLoader() == classloader ) 218 { 219 return ""; 220 } 221 buffer.append( " " ); 222 URL[] urls = classloader.getURLs(); 223 for( int i=0; i<urls.length; i++ ) 224 { 225 String path = urls[i].toString(); 226 if( !path.startsWith( "file:" ) ) 227 { 228 buffer.append( path ); 229 buffer.append( " " ); 230 } 231 } 232 return buffer.toString().trim(); 233 } 234 235 /** 236 * Return a string representing of the classloader. 237 * @param expanded if true return an expanded representation of the classloader 238 * @return the string representation 239 */ 240 public String toString( boolean expanded ) 241 { 242 StringBuffer buffer = new StringBuffer(); 243 listClasspath( buffer ); 244 return buffer.toString(); 245 } 246 247 //-------------------------------------------------------------------- 248 // Object 249 //-------------------------------------------------------------------- 250 251 /** 252 * Return a string representing of the classloader. 253 * @return the string representation 254 */ 255 public String toString() 256 { 257 final String label = 258 getClass().getName() 259 + "#" 260 + System.identityHashCode( this ); 261 return label; 262 } 263 264 /** 265 * Internal operation to list the classloader classpath. 266 * @param buffer the buffer to list to 267 */ 268 protected void listClasspath( StringBuffer buffer ) 269 { 270 listClasspath( buffer, this ); 271 buffer.append( "\n" ); 272 } 273 274 /** 275 * Internal operation to list a classloader classpath. 276 * @param buffer the buffer to list to 277 * @param classloader the classloader to list 278 */ 279 protected void listClasspath( StringBuffer buffer, ClassLoader classloader ) 280 { 281 String label = 282 "\nClassLoader: " 283 + classloader.getClass().getName() 284 + " (" 285 + System.identityHashCode( classloader ) 286 + ")"; 287 288 if( classloader instanceof StandardClassLoader ) 289 { 290 StandardClassLoader cl = (StandardClassLoader) classloader; 291 ClassLoader parent = cl.getParent(); 292 if( null != parent ) 293 { 294 listClasspath( buffer, parent ); 295 } 296 297 if( null != m_name ) 298 { 299 label = label.concat( "\nLabel: " + cl.m_name + " " + cl.getCategory() ); 300 } 301 else 302 { 303 label = label.concat( "\nCategory: " + cl.getCategory() ); 304 } 305 buffer.append( label ); 306 buffer.append( "\n" ); 307 appendEntries( buffer, cl ); 308 } 309 else if( classloader instanceof URLClassLoader ) 310 { 311 URLClassLoader cl = (URLClassLoader) classloader; 312 ClassLoader parent = cl.getParent(); 313 if( null != parent ) 314 { 315 listClasspath( buffer, parent ); 316 } 317 buffer.append( label ); 318 appendEntries( buffer, cl ); 319 } 320 else 321 { 322 buffer.append( label ); 323 buffer.append( "]\n" ); 324 } 325 } 326 327 private static void appendEntries( StringBuffer buffer, URLClassLoader classloader ) 328 { 329 URL[] urls = classloader.getURLs(); 330 for( int i=0; i < urls.length; i++ ) 331 { 332 buffer.append( "\n " ); 333 URL url = urls[i]; 334 String spec = url.toString(); 335 buffer.append( spec ); 336 } 337 buffer.append( "\n" ); 338 } 339 340 /** 341 * Return a string representing a report fo the common classloader chain 342 * following by the primary annd seciondarty classloaders. 343 * @param primary the primary classloader 344 * @param secondary the secondary classloader 345 * @return the report 346 */ 347 public static String toString( ClassLoader primary, ClassLoader secondary ) 348 { 349 StringBuffer buffer = new StringBuffer(); 350 ClassLoader anchor = getCommonParent( primary, secondary ); 351 if( null != anchor ) 352 { 353 buffer.append( "\n----------------------------------------------------------------" ); 354 buffer.append( "\nCommon Classloader" ); 355 buffer.append( "\n----------------------------------------------------------------" ); 356 list( buffer, anchor ); 357 } 358 if( null != primary ) 359 { 360 buffer.append( "\n----------------------------------------------------------------" ); 361 buffer.append( "\nPrimary Classloader" ); 362 buffer.append( "\n----------------------------------------------------------------" ); 363 list( buffer, primary, anchor ); 364 } 365 if( null != secondary ) 366 { 367 buffer.append( "\n----------------------------------------------------------------" ); 368 buffer.append( "\nSecondary Classloader" ); 369 buffer.append( "\n----------------------------------------------------------------" ); 370 list( buffer, secondary, anchor ); 371 } 372 buffer.append( "\n----------------------------------------------------------------" ); 373 return buffer.toString(); 374 } 375 376 private static ClassLoader getCommonParent( ClassLoader primary, ClassLoader secondary ) 377 { 378 ClassLoader[] primaryChain = getClassLoaderChain( primary ); 379 ClassLoader[] secondaryChain = getClassLoaderChain( secondary ); 380 return getCommonClassLoader( primaryChain, secondaryChain ); 381 } 382 383 private static ClassLoader[] getClassLoaderChain( ClassLoader classloader ) 384 { 385 if( null == classloader ) 386 { 387 return new ClassLoader[0]; 388 } 389 else 390 { 391 ArrayList list = new ArrayList(); 392 list.add( classloader ); 393 ClassLoader parent = classloader.getParent(); 394 while( null != parent ) 395 { 396 list.add( parent ); 397 parent = parent.getParent(); 398 } 399 ArrayList result = new ArrayList(); 400 int n = list.size() - 1; 401 for( int i=n; i>-1; i-- ) 402 { 403 result.add( list.get( i ) ); 404 } 405 return (ClassLoader[]) result.toArray( new ClassLoader[0] ); 406 } 407 } 408 409 private static ClassLoader getCommonClassLoader( ClassLoader[] primary, ClassLoader[] secondary ) 410 { 411 ClassLoader anchor = null; 412 for( int i=0; i<primary.length; i++ ) 413 { 414 ClassLoader classloader = primary[i]; 415 if( secondary.length > i ) 416 { 417 ClassLoader cl = secondary[i]; 418 if( classloader == cl ) 419 { 420 anchor = cl; 421 } 422 else 423 { 424 return anchor; 425 } 426 } 427 else 428 { 429 return anchor; 430 } 431 } 432 return anchor; 433 } 434 435 private static void list( StringBuffer buffer, ClassLoader classloader ) 436 { 437 list( buffer, classloader, null ); 438 } 439 440 private static void list( StringBuffer buffer, ClassLoader classloader, ClassLoader anchor ) 441 { 442 if( classloader == anchor ) 443 { 444 return; 445 } 446 ClassLoader parent = classloader.getParent(); 447 if( null != parent ) 448 { 449 list( buffer, parent, anchor ); 450 } 451 String label = 452 "\nClassLoader: " 453 + classloader.getClass().getName() 454 + " (" + System.identityHashCode( classloader ) + ")"; 455 buffer.append( label ); 456 if( classloader instanceof StandardClassLoader ) 457 { 458 StandardClassLoader loader = (StandardClassLoader) classloader; 459 if( null != loader.m_name ) 460 { 461 buffer.append( "\nLabel: " + loader.m_name + " " + loader.m_category ); 462 } 463 else 464 { 465 buffer.append( "\nCategory: " + loader.m_category ); 466 } 467 } 468 if( classloader instanceof URLClassLoader ) 469 { 470 URLClassLoader urlcl = (URLClassLoader) classloader; 471 buffer.append( "\n" ); 472 appendEntries( buffer, urlcl ); 473 } 474 } 475 476 /** 477 * Handle notification of the creation of a new classloader. 478 * @param logger the logging channel 479 * @param label the classloader label 480 * @param category the classloader category 481 * @param classloader the new classloader to report 482 */ 483 private static void classloaderConstructed( Logger logger, String label, Category category, ClassLoader classloader ) 484 { 485 if( logger.isTraceEnabled() ) 486 { 487 int id = System.identityHashCode( classloader ); 488 StringBuffer buffer = new StringBuffer(); 489 buffer.append( "new " ); 490 buffer.append( category.toString() ); 491 buffer.append( " classloader for " + label ); 492 buffer.append( "\n id: " + id ); 493 ClassLoader parent = classloader.getParent(); 494 if( null != parent ) 495 { 496 int pid = System.identityHashCode( parent ); 497 buffer.append( 498 "\n extends: " 499 + pid ); 500 } 501 if( classloader instanceof URLClassLoader ) 502 { 503 URLClassLoader loader = (URLClassLoader) classloader; 504 URL[] urls = loader.getURLs(); 505 if( urls.length == 1 ) 506 { 507 buffer.append( 508 "\n contains: 1 entry" ); 509 } 510 else 511 { 512 buffer.append( 513 "\n contains: " 514 + urls.length 515 + " entries" ); 516 } 517 for( int i=0; i < urls.length; i++ ) 518 { 519 URL url = urls[i]; 520 buffer.append( 521 "\n [" 522 + ( i+1 ) 523 + "] " 524 + url.toString() ); 525 } 526 } 527 logger.trace( buffer.toString() ); 528 } 529 } 530 }