/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.compiler.protocols.gosuclass; import gw.internal.gosu.compiler.GosuClassLoader; import gw.internal.gosu.compiler.SingleServingGosuClassLoader; import gw.internal.gosu.ir.TransformingCompiler; import gw.internal.gosu.parser.GosuClass; import gw.internal.gosu.parser.IGosuClassInternal; import gw.lang.reflect.IHasJavaClass; import gw.lang.reflect.IType; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.ICompilableType; import gw.lang.reflect.gs.IGosuProgram; import gw.lang.reflect.java.IJavaBackedType; import gw.lang.reflect.module.IModule; import gw.lang.reflect.module.TypeSystemLockHelper; import gw.util.GosuExceptionUtil; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.util.HashSet; import java.util.Set; /** */ public class GosuClassesUrlConnection extends URLConnection { private static final String[] JAVA_NAMESPACES_TO_IGNORE = { "java/", "javax/", "sun/" }; private ICompilableType _type; private boolean _bInterfaceAnnotationMethods; private boolean _bDirectory; private boolean _bInvalid; //public static final ConcurrentHashMap<URL, URL> _visited = new ConcurrentHashMap<URL, URL>(); private static ThreadLocal<Set<String>> INITIATED_BY_HANDLER = new ThreadLocal<Set<String>>() { @Override protected Set<String> initialValue() { return new HashSet<String>(); } }; public GosuClassesUrlConnection( URL url ) { super( url ); } @Override public void connect() throws IOException { if( _bInvalid ) { throw new IOException(); } connectImpl(); if( _bInvalid ) { throw new IOException(); } } private boolean connectImpl() { if( _bInvalid ) { return false; } if( _type == null && !_bDirectory ) { String strPath = URLDecoder.decode(getURL().getPath()); String strClass = strPath.substring( 1 ); if( !ignoreJavaClass( strClass ) ) { String strType = strClass.replace( '/', '.' ); int iIndexClass = strType.lastIndexOf( ".class" ); if( iIndexClass > 0 ) { strType = strType.substring( 0, iIndexClass ).replace( '$', '.' ); if( strType.endsWith( '.' + GosuClass.ANNOTATION_METHODS_FOR_INTERFACE_INNER_CLASS ) ) { _bInterfaceAnnotationMethods = true; strType = strType.substring( 0, strType.lastIndexOf( '.' ) ); } maybeAssignGosuType( strType ); } else if( strPath.endsWith( "/" ) ) { _bDirectory = true; } } _bInvalid = _type == null && !_bDirectory; } return !_bInvalid; } private void maybeAssignGosuType( String strType ) { if( strType.contains( IGosuProgram.NAME_PREFIX + "eval_" ) ) { // Never load an eval class here, they should always load in a single-serving loader return; } ClassLoader loader = TypeSystem.getGosuClassLoader().getActualLoader(); TypeSystemLockHelper.getTypeSystemLockWithMonitor( loader ); try { IModule global = TypeSystem.getGlobalModule(); IType type; TypeSystem.pushModule(global); try { type = TypeSystem.getByFullNameIfValidNoJava( strType ); } finally { TypeSystem.popModule(global); } if( type instanceof ICompilableType ) { if( !isInSingleServingLoader( type.getEnclosingType() ) ) { _type = (ICompilableType)type; } } } finally { TypeSystem.unlock(); } } private boolean isInSingleServingLoader( IType type ) { if( type instanceof IJavaBackedType ) { return ((IJavaBackedType)type).getBackingClass().getClassLoader() instanceof SingleServingGosuClassLoader; } if( type instanceof IHasJavaClass ) { return ((IHasJavaClass)type).getBackingClass().getClassLoader() instanceof SingleServingGosuClassLoader; } return false; } public static boolean isInitiatedByHandler(String strName) { return INITIATED_BY_HANDLER.get().contains(strName); } private boolean ignoreJavaClass( String strClass ) { for( String namespace: JAVA_NAMESPACES_TO_IGNORE ) { if( strClass.startsWith( namespace ) ) { return true; } } return false; } @Override public InputStream getInputStream() throws IOException { if( _type != null ) { // Avoid compiling until the bytes are actually requested; // sun.misc.URLClassPath grabs the inputstream twice, the first time is for practice :) return new LazyByteArrayInputStream(); } else if( _bDirectory ) { return new ByteArrayInputStream( new byte[0] ); } throw new IOException( "Invalid or missing Gosu class for: " + url.toString() ); } public boolean isValid() { return connectImpl(); } class LazyByteArrayInputStream extends InputStream { protected byte _buf[]; protected int _pos; protected int _mark; protected int _count; private void init() { if( _buf == null ) { ClassLoader loader = TypeSystem.getGosuClassLoader().getActualLoader(); TypeSystemLockHelper.getTypeSystemLockWithMonitor( loader ); try { if( _bInterfaceAnnotationMethods ) { _buf = TransformingCompiler.compileInterfaceMethodsClass( (IGosuClassInternal)_type, false ); } else { //System.out.println( "Compiling: " + _type.getName() ); _buf = GosuClassLoader.instance().getBytes( _type); } _pos = 0; _count = _buf.length; } catch( Throwable e ) { // log the exception, it tends to get swollowed esp. if it's the class doesn't parse e.printStackTrace(); throw GosuExceptionUtil.forceThrow( e ); } finally { TypeSystem.unlock(); } } } public int read() { init(); return (_pos < _count) ? (_buf[_pos++] & 0xff) : -1; } @Override public int read( byte[] b ) throws IOException { init(); return super.read( b ); } public int read( byte b[], int off, int len ) { init(); if( b == null ) { throw new NullPointerException(); } else if( off < 0 || len < 0 || len > b.length - off ) { throw new IndexOutOfBoundsException(); } if( _pos >= _count ) { return -1; } if( _pos + len > _count ) { len = _count - _pos; } if( len <= 0 ) { return 0; } System.arraycopy( _buf, _pos, b, off, len ); _pos += len; return len; } public long skip( long n ) { if( _pos + n > _count ) { n = _count - _pos; } if( n < 0 ) { return 0; } _pos += n; return n; } public int available() { init(); return _count - _pos; } public boolean markSupported() { return true; } public void mark( int readAheadLimit ) { _mark = _pos; } public void reset() { _pos = _mark; } public void close() throws IOException { } } }