All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gw.lang.Gosu Maven / Gradle / Ivy

There is a newer version: 1.18.2
Show newest version
/*
 * Copyright 2014 Guidewire Software, Inc.
 */

package gw.lang;

import gw.config.CommonServices;
import gw.fs.IDirectory;
import gw.lang.gosuc.GosucUtil;
import gw.lang.init.ClasspathToGosuPathEntryUtil;
import gw.lang.init.GosuInitialization;
import gw.lang.parser.GosuParserFactory;
import gw.lang.parser.IFileContext;
import gw.lang.parser.IGosuProgramParser;
import gw.lang.parser.IParseResult;
import gw.lang.parser.ParserOptions;
import gw.lang.parser.StandardSymbolTable;
import gw.lang.parser.exceptions.ParseResultsException;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.Modifier;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuProgram;
import gw.lang.reflect.java.JavaTypes;
import gw.util.GosuExceptionUtil;
import gw.util.OSPlatform;
import gw.util.PathUtil;
import gw.util.StreamUtil;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import gw.util.concurrent.LocklessLazyVar;
import manifold.util.JreUtil;
import manifold.util.NecessaryEvilUtil;
import manifold.util.ReflectUtil;
//import sun.misc.URLClassPath;

public class Gosu
{
  private static final LocklessLazyVar> BUILTIN_CLASSLOADER =
    LocklessLazyVar.make( () -> ReflectUtil.type( "jdk.internal.loader.BuiltinClassLoader" ) );

  /**
   * used as a virtual package e.g., for scratchpad
   */
  public static final String NOPACKAGE = "_nopackage_";
  public static final String GOSU_SCRATCHPAD_FQN = NOPACKAGE + ".GosuScratchpad";
  public static final String JAR_REPO_DIR = "JAR-REPO";     //!! if you change this, also change it in Launcher
  public static final String JAR_REPO_TXT = "jar-repo.txt"; //!! "
  public static final String FAILED = "  FAILED: ";
  public static final String SUCCESS = "  SUCCESS ";

  private static List _classpath;
  private static File _script;
  private static List _rawArgs;

  public static void main( String[] args )
  {
    start( args );
  }

  private static void checkArgsLength( int i, int length )
  {
    if( i >= length )
    {
      showHelpAndQuit();
    }
  }

  private static void start( String[] args )
  {
    try
    {
      if( args.length == 0 )
      {
        launchEditor();
        return;
      }
      int i = 0;
      checkArgsLength( i, args.length );
      String cpValue = null;
      boolean cmdLineCP = false;
      if( args[i].equals( "-checkedArithmetic" ) )
      {
        i++;
        checkArgsLength( i, args.length );
        System.setProperty( "checkedArithmetic", "true" );
      }

      if( args[i].equals( "-classpath" ) )
      {
        cmdLineCP = true;
        i += 2;
        checkArgsLength( i, args.length );
        cpValue = args[i - 1];
      }

      switch( args[i] )
      {
        case "-fqn":
        {
          List classpath = makeClasspath( cpValue, "", cmdLineCP );
          init( classpath );
          runWithType( args[i + 1], collectArgs( i + 2, args ) );
          break;
        }

        case "-e":
        {
          List classpath = makeClasspath( cpValue, "", cmdLineCP );
          init( classpath );
          runWithInlineScript( args[i + 1], collectArgs( i + 2, args ) );
          break;
        }

        default:
        {
          File script = new File( args[i] );
          if( !script.isFile() || !script.exists() )
          {
            showHelpAndQuit();
          }
          if( cpValue == null )
          {
            cpValue = extractClassPathFromSrc( script.getAbsolutePath() );
          }
          List classpath = makeClasspath( cpValue, script.getAbsoluteFile().getParent(), cmdLineCP );
          init( classpath );
          runWithFile( script, collectArgs( i + 1, args ) );
          break;
        }
      }
    }
    catch( Throwable t )
    {
      t.printStackTrace( System.err );
    }
  }

  private static void launchEditor() throws Exception
  {
    Class cls = Class.forName( "editor.RunMe" );
    Method m = cls.getMethod( "launchEditor" );
    m.invoke( null );
  }

  private static List collectArgs( int i, String[] args )
  {
    List scriptArgs = new ArrayList<>();
    if( args != null )
    {
      while( i < args.length )
      {
        scriptArgs.add( args[i] );
        i++;
      }
    }
    return scriptArgs;
  }

  private static String extractClassPathFromSrc( String file )
  {
    BufferedReader br = null;
    String line;
    String ret = null;

    try
    {
      br = new BufferedReader( new FileReader( file ) );
      //noinspection StatementWithEmptyBody
      while( (line = br.readLine()).trim().isEmpty() )
      {
        ; //ignore
      }
      if( line.startsWith( "classpath" ) )
      {
        int b = line.indexOf( '"' );
        if( b != -1 )
        {
          int e = line.indexOf( '"', b + 1 );
          if( e != -1 )
          {
            ret = line.substring( b + 1, e );
          }
        }
      }
    }
    catch( IOException e )
    { //ignore
    }
    finally
    {
      try
      {
        if( br != null )
        {
          br.close();
        }
      }
      catch( IOException ex )
      { //ignore
      }
    }
    return ret;
  }

  private static List makeClasspath( String cpValue, String scriptRoot, boolean cmdLineCP )
  {
    ArrayList cp = new ArrayList<>();
    if( cpValue != null )
    {
      StringTokenizer st = new StringTokenizer( cpValue, ",", false );
      while( st.hasMoreTokens() )
      {
        String s = st.nextToken();
        if( (s.contains( ":" ) && !OSPlatform.isWindows()) || s.contains( ";" ) )
        {
          for( StringTokenizer sysTok = new StringTokenizer( cpValue, File.pathSeparator, false ); sysTok.hasMoreTokens(); )
          {
            s = sysTok.nextToken();
            String pathname = cmdLineCP
                              ? s
                              : scriptRoot + File.separatorChar + s;
            cp.add( CommonServices.getFileSystem().getIDirectory( new File( pathname ) ) );
          }
        }
        else
        {
          String pathname = cmdLineCP ? s : scriptRoot + File.separatorChar + s;
          cp.add( CommonServices.getFileSystem().getIDirectory( new File( pathname ) ) );
        }
      }
    }
    return cp;
  }

  // Note this is a giant hack, we need to instead get the type name from the psiClass
  private static String makeFqn( File file )
  {
    String path = file.getAbsolutePath();
    int srcIndex = path.indexOf( "src" + File.separatorChar );
    if( srcIndex >= 0 )
    {
      String fqn = path.substring( srcIndex + 4 ).replace( File.separatorChar, '.' );
      return fqn.substring( 0, fqn.lastIndexOf( '.' ) );
    }
    else
    { // the Gosu Scratchpad case
      String fqn = file.getName();
      fqn = NOPACKAGE + '.' + fqn.substring( 0, fqn.lastIndexOf( '.' ) ).replace( " ", "" );
      return fqn;
    }
  }


  public static void setClasspath( List classpath )
  {
    classpath = new ArrayList<>( classpath );
    removeDups( classpath );

    if( classpath.equals( _classpath ) )
    {
      return;
    }

    _classpath = classpath;
    ClassLoader loader = TypeSystem.getCurrentModule() == null
                         // Can be null if called before the exec environment is setup, so assume the future parent of the module loader is the plugin loader
                         ? CommonServices.getEntityAccess().getPluginClassLoader()
                         : TypeSystem.getGosuClassLoader().getActualLoader();
    if( loader instanceof URLClassLoader )
    {
      for( IDirectory entry : classpath )
      {
        try
        {
          ReflectUtil.method( URLClassLoader.class, "addURL", URL.class )
           .invoke( loader, entry.toURI().toURL() );
        }
        catch( Exception e )
        {
          throw new RuntimeException( e );
        }
      }
    }

    reinitGosu( classpath );
    TypeSystem.refresh( true );
  }

  public static List getClasspath()
  {
    return _classpath;
  }

  private static void reinitGosu( List classpath )
  {
    try
    {
      GosuInitialization.instance( TypeSystem.getExecutionEnvironment() ).reinitializeRuntime( ClasspathToGosuPathEntryUtil.convertClasspathToGosuPathEntries( classpath ) );
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
  }

  private static void removeDups( List classpath )
  {
    for( int i = classpath.size() - 1; i >= 0; i-- )
    {
      IDirectory f = classpath.get( i );
      classpath.remove( i );
      if( !classpath.contains( f ) )
      {
        classpath.add( i, f );
      }
    }
  }

  /**
   * Initializes Gosu using the classpath derived from the current classloader and system classpath.
   */
  public static void init()
  {
    init( null );
  }

  public static void init( List classpath )
  {
    NecessaryEvilUtil.bypassJava9Security();
    List combined = new ArrayList<>();
    if( classpath != null )
    {
      combined.addAll( classpath );
    }
    combined.addAll( deriveClasspathFrom( Gosu.class ) );
    setClasspath( combined );
  }

  public static boolean bootstrapGosuWhenInitiatedViaClassfile()
  {
    if( GosuInitialization.isAnythingInitialized() &&
        GosuInitialization.instance( TypeSystem.getExecutionEnvironment() ).isInitialized() )
    {
      return false;
    }
    init();
    return true;
  }

  static void showHelpAndQuit()
  {
    System.out.println( "Gosu version: " + getVersion() +
                        "\nUsage:\n" +
                        "    gosu [-checkedArithmetic] [-classpath 'entry1,entry2...'] program.gsp [args...]\n" +
                        "    gosu [-checkedArithmetic] [-classpath 'entry1,entry2...'] -e 'inline script' [args...]\n" );
    System.exit( 1 );
  }

  public static List deriveClasspathFrom( Class clazz )
  {
    return deriveClasspathFrom_Java9( clazz );
  }

  public static List deriveClasspathFrom_Java9( Class clazz )
  {
    List ll = new ArrayList<>();
    ClassLoader loader = clazz.getClassLoader();
    if( loader != null && BUILTIN_CLASSLOADER.get().isAssignableFrom( loader.getClass() ) )
    {
      Object ucp = manifold.util.ReflectUtil.field( loader, "ucp" ).get();
      if( ucp != null )
      {
        for( URL url: (URL[])ReflectUtil.method( ucp, "getURLs" ).invoke() )
        {
          try
          {
            IDirectory file = CommonServices.getFileSystem().getIDirectory( Paths.get( url.toURI() ) );
            if( file.exists() )
            {
              ll.add( file );
            }
          }
          catch( Exception e )
          {
            //ignore
          }
        }
      }
    }
    return ll;
  }

  public static GosuVersion getVersion()
  {
    InputStream in = Gosu.class.getClassLoader().getResourceAsStream( GosuVersion.RESOURCE_PATH );
    if( in == null )
    {
      return new GosuVersion( 0, 0 );
    }
    Reader reader = StreamUtil.getInputStreamReader( in );
    return GosuVersion.parse( reader );
  }

  public static File getCurrentProgram()
  {
    return _script;
  }

  public static List getRawArgs()
  {
    return _rawArgs;
  }

  public static void setRawArgs( String[] args )
  {
    _rawArgs = collectArgs( 0, args );
  }

  private static int runWithType( String fqn, List args ) throws Exception
  {
    // set remaining arguments as arguments to the Gosu program
    _rawArgs = args;
    IType type = TypeSystem.getByFullName( fqn );
    if( type instanceof IGosuProgram )
    {
      Object result = ((IGosuProgram)type).getProgramInstance().evaluate( null );
      if( result != null )
      {
        System.out.println( result );
      }
    }
    else
    {
      IMethodInfo mainMethod = hasStaticMain( type );
      if( mainMethod != null )
      {
        gw.lang.reflect.ReflectUtil.invokeStaticMethod( type.getName(), "main", new Object[]{new String[]{}} );
      }
      else if( type instanceof IGosuClass )
      {
        runTest( (IGosuClass)type );
      }
      else
      {
        throw new UnsupportedOperationException( "Don't know how to run: " + fqn );
      }
    }
    return 0;
  }

  public static void runTest( IGosuClass gsType ) throws Exception
  {
    Class cls = gsType.getBackingClass();
    runNamedOrAnnotatedMethod( cls.newInstance(), "beforeClass", "org.junit.BeforeClass" );
    for( Method m : cls.getMethods() )
    {
      if( isTestMethod( m ) )
      {
        Object instance = cls.newInstance();
        runNamedOrAnnotatedMethod( instance, "beforeMethod", "org.junit.Before" );
        try
        {
          System.out.println( "- " + m.getName() );
          m.invoke( instance );
          System.out.println( SUCCESS );
        }
        catch( InvocationTargetException e )
        {
          //noinspection ThrowableResultOfMethodCallIgnored
          Throwable cause = GosuExceptionUtil.findExceptionCause( e );
          if( cause instanceof AssertionError )
          {
            System.out.println( FAILED + cause.getClass().getSimpleName() + " : " + cause.getMessage() );
            String lines = findPertinentLines( gsType, cause );
            System.out.println( lines );
          }
          else
          {
            throw GosuExceptionUtil.forceThrow( cause );
          }
        }
        finally
        {
          runNamedOrAnnotatedMethod( instance, "afterMethod", "org.junit.After" );
          System.out.println();
        }
      }
    }
    runNamedOrAnnotatedMethod( cls.newInstance(), "afterClass", "org.junit.AfterClass" );
    System.out.println();
  }

  private static boolean isTestMethod( Method m ) throws Exception
  {
    int modifiers = m.getModifiers();
    return Modifier.isPublic( modifiers ) &&
           (m.getName().startsWith( "test" ) || hasAnnotation( m, "org.junit.Test" )) &&
           m.getParameters().length == 0;
  }

  private static void runNamedOrAnnotatedMethod( Object instance, String methodName, String annoName ) throws Exception
  {
    for( Method m : instance.getClass().getMethods() )
    {
      if( m.getName().equals( methodName ) )
      {
        m.invoke( instance );
        return;
      }
      for( Annotation anno : m.getAnnotations() )
      {
        if( anno.annotationType().getName().equals( annoName ) )
        {
          m.invoke( instance );
          return;
        }
      }
    }
  }

  private static boolean hasAnnotation( Method m, String name ) throws Exception
  {
    for( Annotation anno : m.getAnnotations() )
    {
      if( anno.annotationType().getName().equals( name ) )
      {
        return true;
      }
    }
    return false;
  }

  private static IMethodInfo hasStaticMain( IType type )
  {
    IMethodInfo main = type.getTypeInfo().getMethod( "main", JavaTypes.STRING().getArrayType() );
    if( main != null && main.isStatic() && main.getReturnType() == JavaTypes.pVOID() )
    {
      return main;
    }
    return null;
  }

  private static void runWithFile( File script, List args ) throws IOException, ParseResultsException
  {
    _script = script;
    // set remaining arguments as arguments to the Gosu program
    _rawArgs = args;
    byte[] bytes = StreamUtil.getContent( new BufferedInputStream( new FileInputStream( script ) ) );
    String content = StreamUtil.toString( bytes );
    IFileContext ctx = new ProgramFileContext( script, makeFqn( script ) );
    IGosuProgramParser programParser = GosuParserFactory.createProgramParser();
    ParserOptions options = new ParserOptions().withFileContext( ctx );
    IParseResult result = programParser.parseExpressionOrProgram( content, new StandardSymbolTable( true ), options );
    IGosuProgram program = result.getProgram();
    Object ret = program.getProgramInstance().evaluate( null ); // evaluate it
    IType expressionType = result.getType();
    if( expressionType != null && !JavaTypes.pVOID().equals( expressionType ) )
    {
      GosuShop.print( ret );
    }
  }

  private static void runWithInlineScript( String script, List args ) throws IOException, ParseResultsException
  {
    _script = null;
    // set remaining arguments as arguments to the Gosu program
    _rawArgs = args;
    IGosuProgramParser programParser = GosuParserFactory.createProgramParser();
    IParseResult result = programParser.parseExpressionOrProgram( script, new StandardSymbolTable( true ), new ParserOptions() );
    IGosuProgram program = result.getProgram();
    Object ret = program.getProgramInstance().evaluate( null ); // evaluate it
    IType expressionType = result.getType();
    if( expressionType != null && !JavaTypes.pVOID().equals( expressionType ) )
    {
      GosuShop.print( ret );
    }
  }

  private static String findPertinentLines( IGosuClass gsType, Throwable cause )
  {
    StringBuilder sb = new StringBuilder();
    StackTraceElement[] trace = cause.getStackTrace();
    for( int i = 0; i < trace.length; i++ )
    {
      StackTraceElement elem = trace[i];
      if( elem.getClassName().equals( gsType.getName() ) )
      {
        sb.append( "     at " ).append( elem.toString() ).append( "\n" );
      }
    }
    return sb.toString();
  }

  public static List findJreSources()
  {
    List sources = new ArrayList<>();
    Arrays.asList( "src.zip", "javafx-src.zip" ).forEach( fileName -> findJreSourcePath( sources, fileName ) );
    return sources;
  }

  private static void findJreSourcePath( List sources, String archiveName )
  {
    String javaHomePath = System.getProperty( "java.home" );
    Path javaDir = PathUtil.create( javaHomePath );
    if( PathUtil.getName( javaDir ).equalsIgnoreCase( "jre" ) )
    {
      javaDir = javaDir.getParent();
    }
    Path srcZip = PathUtil.create( javaDir, archiveName );
    if( PathUtil.isFile( srcZip ) )
    {
      sources.add( srcZip.toString() );
    }
    else
    {
      String javaDirName = PathUtil.getName( javaDir );
      if( javaDirName.startsWith( "jre" ) )
      {
        javaDirName = javaDirName.replace( "jre", "jdk" );
        Path jdkSrc = PathUtil.create( javaDirName, archiveName );
        if( PathUtil.isFile( jdkSrc ) )
        {
          sources.add( jdkSrc.toString() );
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy