package gw.lang.shell; import gw.config.CommonServices; import gw.config.Registry; import gw.lang.cli.CommandLineAccess; import gw.lang.init.ClasspathToGosuPathEntryUtil; import gw.lang.init.GosuInitialization; import gw.lang.parser.GosuParserFactory; import gw.lang.parser.IFileContext; import gw.lang.parser.IGosuParser; import gw.lang.parser.IGosuProgramParser; import gw.lang.parser.IParseIssue; import gw.lang.parser.IParseResult; import gw.lang.parser.IScriptPartId; import gw.lang.parser.ISourceCodeTokenizer; import gw.lang.parser.ParserOptions; import gw.lang.parser.ScriptPartId; import gw.lang.parser.StandardSymbolTable; import gw.lang.parser.exceptions.ParseException; import gw.lang.parser.exceptions.ParseResultsException; import gw.lang.parser.exceptions.ParseWarning; import gw.lang.parser.template.ITemplateGenerator; import gw.lang.reflect.IType; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.GosuClassTypeLoader; import gw.lang.reflect.gs.GosuTemplateTypeLoader; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.IGosuProgram; import gw.lang.reflect.gs.ITemplateType; import gw.lang.reflect.java.IJavaType; import gw.lang.reflect.module.IModule; import gw.util.GosuExceptionUtil; import gw.util.IGosuEditor; import gw.util.Stack; import gw.util.StreamUtil; import gw.util.filewatcher.FileWatcher; import java.awt.*; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Properties; /** * Copyright 2010 Guidewire Software, Inc. */ public class Gosu { private static IGosuEditor _gosuEditor; private static ArgInfo _argInfo; private static List<File> _classpath; private static final List<String> SOURCE_ROOTS = Arrays.asList( "bin", "src", "test", "test-src", "classes", "gsrc", "gtest" ); @SuppressWarnings({"ConstantConditions"}) public static void main( String... args ) { boolean error = false; try { _argInfo = ArgInfo.parseArgs( replaceQuotes( args ) ); // Note: this is kind of junky, but I put all this code here so that stack traces are cleaner when coming out of // the gosu runtime if( _argInfo.hasErrorMessage() ) { showHelp( _argInfo.getErrorMessage() ); System.exit( -1 ); } if( _argInfo.getMode() == ArgInfo.Mode.VERSION ) { System.out.println(getVersion()); System.exit( 0 ); } for( String propString : _argInfo.getSystemProperties() ) { setSystemProperty( propString ); } if( _argInfo.getMode() == ArgInfo.Mode.HELP ) { showHelp( null ); System.exit( 0 ); } // init registry.xml File sourceFile = null; if( _argInfo.getSource() instanceof File ) { sourceFile = (File)_argInfo.getSource(); } init( sourceFile, _argInfo.getClasspath(), _argInfo.getMode() == ArgInfo.Mode.GUI ); if( _argInfo.getMode() == ArgInfo.Mode.GUI ) { FileWatcher.instance().scanForChangesAndNotify(); launchEditor( (File)_argInfo.getSource() ); } else if( _argInfo.getMode() == ArgInfo.Mode.INTERACTIVE ) { new InteractiveShell(true).run(); } else if( _argInfo.getMode() == ArgInfo.Mode.VERIFY ) { printVerificationResults(); } else if( _argInfo.getMode() == ArgInfo.Mode.EXECUTE ) { // set remaining arguments as arguments to the Gosu program CommandLineAccess.setRawArgs( _argInfo.getArgsList() ); String content; IFileContext ctx = null; if( _argInfo.getSource() instanceof File ) { File file = (File)_argInfo.getSource(); if( file != null && !file.getName().endsWith( ".gsp" ) ) { System.err.println( "Cannot execute: " + file.getAbsoluteFile().getName() + "\nOnly Gosu program (.gsp) files are executable" ); System.exit( -1 ); } content = StreamUtil.toString( StreamUtil.getContent( new FileInputStream( file ) ) ); ctx = new ProgramFileContext( file ); } else if( _argInfo.getSource() instanceof String ) { content = (String)_argInfo.getSource(); } else { content = StreamUtil.toString( StreamUtil.getContent( (InputStream)_argInfo.getSource() ) ); } IGosuProgramParser programParser = GosuParserFactory.createProgramParser(); IParseResult result = programParser.parseExpressionOrProgram( content, new StandardSymbolTable( true ), new ParserOptions() ); IGosuProgram program = result.getProgram(); Object ret = program.getProgramInstance().evaluate(null); // evaluate it IType expressionType = result.getType(); if( expressionType != null && !IJavaType.pVOID.equals( expressionType ) ) { StandardSymbolTable.print( ret ); } } } catch( Throwable t ) { error = true; t.printStackTrace(); } if( error ) { System.exit( -1 ); } } private static void printVerificationResults() { List<IVerificationResults> lst = verifyAllGosu( true, false ); if( lst.size() > 0 ) { for( IVerificationResults result : lst ) { System.out.print( result.getTypeName() ); System.out.println( ":" ); System.out.println( result.getFeedback() ); } } else { System.out.println( "No verification issues were found" ); } } // I can't get IntellJ to pass double quotes to the JVM, it strips them private static String[] replaceQuotes( String[] args ) { if( System.getProperty( "gw.internal.gosu.replaceQuotes" ) != null ) { String[] fixedArgs = new String[args.length]; for( int i = 0; i < args.length; i++ ) { fixedArgs[i] = args[i].replace( '\'', '"' ); } return fixedArgs; } else { return args; } } /** * Sets a system property, based on the supplied key=value pair. The value part is * optional. * * @param keyVal the key or key=value to set */ private static void setSystemProperty( String keyVal ) { String value = ""; int idx = keyVal.indexOf( '=' ); if( idx >= 0 ) { value = keyVal.substring( idx + 1 ); keyVal = keyVal.substring( 0, idx ); } System.setProperty( keyVal, value ); } public static void setClasspath( List<File> classpath ) { addExtDir( classpath ); removeDups( classpath ); boolean bSameClasspath = classpath.equals( _classpath ); _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.getCurrentModule().getClassLoader(); if( loader instanceof IUpdateableClassLoader ) { for( File entry : classpath ) { try { ((IUpdateableClassLoader)loader).addURL( entry.toURI().toURL() ); } catch( MalformedURLException e ) { throw new RuntimeException( e ); } } reinitGosu( classpath ); } List<String> cp = new ArrayList<String>(); for( File f : _classpath ) { cp.add( f.getAbsolutePath() ); } if( !bSameClasspath ) { TypeSystem.refresh( true ); } } private static void addExtDir( List<File> classpath ) { try { URI location = Gosu.class.getProtectionDomain().getCodeSource().getLocation().toURI(); File file = new File( location ); if( file.exists() ) { File ext = new File( file.getParentFile().getParentFile(), "ext" ); if( ext.exists() ) { for( File lib : ext.listFiles() ) { if( lib.getName().endsWith( ".jar" ) ) { classpath.add( lib ); } } } } } catch( URISyntaxException e ) { GosuExceptionUtil.forceThrow( e ); } } private static void reinitGosu( List<File> classpath ) { GosuInitialization.uninitialize(); try { GosuInitialization.initializeRuntime( ClasspathToGosuPathEntryUtil.convertClasspathToGosuPathEntries( classpath ) ); } catch( Exception e ) { e.printStackTrace(); } } private static void removeDups( List<File> classpath ) { for( int i = classpath.size()-1; i >= 0; i-- ) { File 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( Gosu.class ); } public static void init( Class classToDeriveClasspathFrom ) { init( deriveClasspathFrom( classToDeriveClasspathFrom ) ); } public static void init( List<File> classpath ) { init( null, classpath ); } /** * Provides a way to initialize gosu given a particular resource */ public static void init( File sourceFile, List<File> classPath ) { init( sourceFile, classPath, false ); } /** * Provides a way to initialize gosu given a particular resource */ public static void init( File sourceFile, List<File> classPath, boolean inferClassPath ) { classPath.addAll( initRegistry( sourceFile ) ); if( sourceFile == null ) { CommandLineAccess.setCurrentProgram( null ); setClasspath( classPath ); } else { CommandLineAccess.setCurrentProgram( sourceFile ); if( isProgramLikeResource( sourceFile ) ) { ProgramDirectiveInfo parser = ProgramDirectiveParser.parse( sourceFile ); if( parser != null ) { classPath.addAll( getClasspathFromProgram( sourceFile, parser.getClasspaths() ) ); TypeSystem.getExecutionEnvironment().setTypeLoadersFromProgram( parser.getTypeLoaders() ); inferClassPath = false; } } if( inferClassPath ) { setClasspath( makeInferredClassPath( sourceFile, classPath ) ); } else { setClasspath( classPath ); } } } private static List<File> initRegistry( File sourceFile ) { if( isProgramLikeResource( sourceFile ) ) { File possibleReg = new File( sourceFile.getParentFile(), "registry.xml" ); if( possibleReg.exists() ) { try { Registry.setLocation( possibleReg.toURI().toURL() ); ArrayList<File> files = new ArrayList<File>(); List<String> entries = Registry.instance().getClasspathEntries(); if( entries != null ) { for( String entry : entries ) { files.addAll(resolveFilesForPath( sourceFile, entry ) ); } } return files; } catch( MalformedURLException e ) { //ignore } } } // default Registry.setLocation( Gosu.class.getResource( "registry.xml" ) ); return Collections.emptyList(); } private static boolean isProgramLikeResource( File sourceFile ) { if( sourceFile != null ) { String name = sourceFile.getName(); return !(name.endsWith( ".gs" ) || name.endsWith( ".gst" ) || name.endsWith( ".gsx" )); } else { return false; } } /** * @deprecated use init() instead */ public static void initGosu( File sourceFile, List<File> classPath ) { init( sourceFile, classPath ); } private static List<File> makeInferredClassPath( File src, List<File> cp ) { ArrayList<File> finalClassPath = new ArrayList<File>( cp ); File sourceRoot = findSourceRoot( src ); if( sourceRoot.exists() ) { finalClassPath.add( sourceRoot ); File projectRoot = sourceRoot.getParentFile(); for( String name : SOURCE_ROOTS ) { File file = new File( projectRoot, name ); if( file.exists() && !finalClassPath.contains( file ) ) { finalClassPath.add( file ); } } for( String name : Arrays.asList( "jars", "lib", "support" ) ) { File file = new File( projectRoot, name ); if( file.exists() ) { for( File possibleJar : file.listFiles() ) { if( possibleJar.getName().endsWith( ".jar" ) && !finalClassPath.contains( possibleJar )) { finalClassPath.add( possibleJar ); } } } } } return finalClassPath; } private static File findSourceRoot( File src ) { src = src.getAbsoluteFile(); File parent = null; // First attempt to extract the source root from the package statement if(src.getName().endsWith( ".gs" ) || src.getName().endsWith( ".gsx" )) { parent = extractSourceRootFromFile( src ); if( parent != null ) { return parent; } } // If that didn't work, climb up the file hierarchy, looking for a likely root parent = src.getParentFile(); // First climb up the parents looking for a standard source root while( parent != null && !SOURCE_ROOTS.contains( src.getName() ) ) { parent = parent.getParentFile(); } // finally, assume that the root is up two directories, as long as that isn't the system root parent = src.getParentFile(); if( parent.getParentFile() == null || parent.getParentFile().getParent() == null ) { return parent; } else { return parent.getParentFile(); } } private static File extractSourceRootFromFile( File src ) { try { ISourceCodeTokenizer tokenizer = CommonServices.getGosuIndustrialPark().createSourceCodeTokenizer( new FileReader( src ) ); while( tokenizer.nextToken() != ISourceCodeTokenizer.TT_EOF ) { if( tokenizer.getTokenAsString().equals( "package" ) ) { Stack<String> parents = new Stack<String>(); while( tokenizer.nextToken() == ISourceCodeTokenizer.TT_WORD ) { parents.push( tokenizer.getTokenAsString() ); if( tokenizer.nextToken() != '.' ) { File parentFile = src.getParentFile(); while( !parents.isEmpty() ) { if( parentFile.getName().equals( parents.pop() ) ) { parentFile = parentFile.getParentFile(); if( parents.isEmpty() ) { return parentFile; } } else { break; } } } } } } } catch( IOException e ) { //ignore } return null; } public static List<IVerificationResults> verifyAllGosu( boolean includeWarnings, boolean log ) { List<String> sortedNames = getAllGosuTypeNames(); ArrayList<IVerificationResults> errors = new ArrayList<IVerificationResults>(); int count = 0; int i = 0; int cutoff = sortedNames.size() / 10; DecimalFormat decimalFormat = new DecimalFormat( "#0.0" ); if( log ) { System.out.println( "Verifying" ); } for( Object o : sortedNames ) { i++; count++; if( i > cutoff ) { i = 0; if( log ) { double v = (double)count * 100.0; System.out.println( decimalFormat.format( v / (double) sortedNames.size()) + "% done." ); } } verifyType( includeWarnings, errors, o, (CharSequence)o ); } return errors; } private static List<String> getAllGosuTypeNames() { List<String> sortedNames = new ArrayList<String>(); for( CharSequence c : TypeSystem.getTypeLoader( GosuClassTypeLoader.class ).getAllTypeNames() ) { String name = c.toString(); if( !name.startsWith( "gw." ) ) { sortedNames.add( name ); } } for( CharSequence c : TypeSystem.getTypeLoader( GosuTemplateTypeLoader.class ).getAllTypeNames() ) { String name = c.toString(); if( !name.startsWith( "gw." ) ) { sortedNames.add( name ); } } return sortedNames; } private static void verifyType( boolean includeWarnings, ArrayList<IVerificationResults> errors, Object o, CharSequence typeName ) { try { IType type = TypeSystem.getByFullNameIfValid( o.toString() ); if( type instanceof IGosuClass ) { boolean valid = type.isValid(); List<IParseIssue> parseIssues = ((IGosuClass)type).getParsedElement().getParseIssues(); if( parseIssues.size() > 0 && (!valid || includeWarnings) ) { errors.add( new GosuTypeVerificationResults( typeName.toString(), parseIssues ) ); } } else if( type instanceof ITemplateType ) { ITemplateGenerator generator = ((ITemplateType) type).getTemplateGenerator(); try { generator.verify(GosuParserFactory.createParser(null)); } catch ( ParseResultsException e) { errors.add( new GosuTypeVerificationResults( typeName.toString(), e.getParseIssues() ) ); } } } catch( Throwable e ) { errors.add( new ExceptionTypeVerificationResults( typeName.toString(), e.getMessage() ) ); } } private static List<File> getClasspathFromProgram( File sourceFile, List<String> classpaths ) { List<File> fpaths = new ArrayList<File>(); for( String strPath : classpaths ) { fpaths.addAll( resolveFilesForPath( sourceFile, strPath ) ); } return fpaths; } public static Object evalProgramOrExpression( List<File> classpath, String snippet ) throws Exception { init( classpath ); IGosuParser parser = GosuParserFactory.createParser( snippet, new StandardSymbolTable( true ) ); return parser.parseExpOrProgram( null, false, true ).evaluate(); } static void showHelp( String msg ) { if( msg != null ) { System.err.println( msg ); } System.err.println( "usage: gosu [options] [file [args...]]" ); System.err.println( " -g [file]" ); System.err.println( " Launch the graphical editor" ); System.err.println( " -i" ); System.err.println( " Run in interactive mode (the default)" ); System.err.println( " -e expr" ); System.err.println( " The rest of the command-line is evaluated as an expression" ); System.err.println( " -" ); System.err.println( " Read from standard input" ); System.err.println( " file" ); System.err.println( " The name of a Gosu source file. Only program (.gsp) files are executable, but you can edit any Gosu source file: (<program>.gsp, <class>.gs, <enhancement>.gsx, <template>.gst" ); System.err.println( " args" ); System.err.println( " Arguments to the Gosu program" ); System.err.println( " -classpath cp" ); System.err.println( " Appends entries to the JVM's classpath" ); System.err.println( " -v" ); System.err.println( " Verify all types" ); System.err.println( " -version" ); System.err.println( " Show the version of Gosu (from VERSION.txt)" ); System.err.println( " -Dkey" ); System.err.println( " -Dkey=value" ); System.err.println( " Sets a system property" ); } public static IGosuEditor getEditorFrame() { return _gosuEditor; } private static void launchEditor( final File file ) throws Exception { EventQueue.invokeLater( new Runnable() { public void run() { setClasspath( _classpath ); // necessary to set up the module correctly in the AWT thread _gosuEditor = CommonServices.getGosuIndustrialPark().createGosuEditor(); _gosuEditor.openInitialFile( makePartId(file), file == null ? null : file.getAbsoluteFile() ); _gosuEditor.showMe(); } } ); //noinspection InfiniteLoopStatement while( true ) { Thread.sleep( Integer.MAX_VALUE ); // Wait forever. Closing GUI will cause program to exit } } public static IScriptPartId makePartId( File file ) { IModule currentModule = TypeSystem.getCurrentModule(); if( file == null ) { return new ScriptPartId( "New Program", null ); } else if( file.getName().endsWith( ".gs" ) || file.getName().endsWith( ".gsx" ) ) { String classNameForFile = currentModule.getClassNameForFile( file ); return new ScriptPartId( classNameForFile, null ); } else if( file.getName().endsWith( ".gst" ) ) { String templateNameForFile = currentModule.getTemplateNameForFile( file ); return new ScriptPartId( templateNameForFile, null ); } else if( file.getName().endsWith( ".gsp" ) ) { String programNameForFile = currentModule.getProgramNameForFile( file ); return new ScriptPartId( programNameForFile, null ); } else { return new ScriptPartId( "Unknown Resource Type", null ); } } private static List<File> resolveFilesForPath( File programFile, String strPath ) { File file = null; //resolve relative paths relative to the executable, rather than the current working directory if( strPath.startsWith( "." ) ) { String path = null; try { path = programFile.getCanonicalFile().getParentFile().getAbsolutePath() + File.separator + strPath; file = new File( path ); } catch( IOException e ) { throw new RuntimeException( "Could not resolve file relative to the executable with path \"" + path + "\"", e ); } } //If it was not an obvious relative path, attempt to resolve it as an absolute path if( file == null ) { file = new File( strPath ); //If it is not an absolute path, try it as a (non-obvious) relative path if( !file.exists() ) { String path = null; try { path = programFile.getCanonicalFile().getParentFile().getAbsolutePath() + File.separator + strPath; file = new File( path ); } catch( IOException e ) { throw new RuntimeException( "Could not resolve file relative to the executable with path \"" + path + "\"", e ); } } } try { file = file.getCanonicalFile(); } catch (IOException e) { //ignore } if( file.exists() && file.isDirectory() ) { File[] files = file.listFiles( new FilenameFilter() { public boolean accept( File dir, String name ) { return name.endsWith( ".jar" ); } } ); ArrayList<File> returnFiles = new ArrayList<File>(); returnFiles.add( file ); returnFiles.addAll( Arrays.asList( files ) ); return returnFiles; } else { return file.exists() ? Arrays.asList( file ) : Collections.<File>emptyList(); } } public static List<File> deriveClasspathFrom( Class clazz ) { List<File> ll = new LinkedList<File>(); ClassLoader loader = clazz.getClassLoader(); while( loader != null ) { if( loader instanceof URLClassLoader ) { for( URL url : ((URLClassLoader)loader).getURLs() ) { try { File file = new File( url.toURI() ); if( file.exists() ) { ll.add( file ); } } catch( Exception e ) { //ignore } } } loader = loader.getParent(); } return ll; } public static List<File> getClasspath() { return _classpath; } public static String getVersion() { try { URL location = Gosu.class.getProtectionDomain().getCodeSource().getLocation(); File jar = new File( location.toURI() ); File versionInfo = new File( jar.getParentFile().getParentFile(), "VERSION.txt" ); if( versionInfo.exists() && versionInfo.isFile() ) { Properties props = new Properties(); props.load( new FileReader( versionInfo ) ); return props.getProperty( "Version" ); } } catch( Exception e ) { //ignore } return "Unknown"; } public interface IVerificationResults { public boolean containsError(); public String getFeedback(); public String getTypeName(); } private static class GosuTypeVerificationResults implements IVerificationResults { private final String _typeName; private final List<IParseIssue> _parseIssues; public GosuTypeVerificationResults( String typeName, List<IParseIssue> parseIssues ) { _typeName = typeName; _parseIssues = parseIssues; } @Override public boolean containsError() { return true; } @Override public String getFeedback() { StringBuilder builder = new StringBuilder(); List<ParseWarning> warnings = getWarnings(); if( warnings.size() > 0 ) { builder.append( "Warnings :\n\n" ); for( ParseWarning warning : warnings ) { builder.append( warning.getConsoleMessage() ); builder.append( "\n" ); } } List<ParseException> errors = getErrors(); if( errors.size() > 0 ) { builder.append( "Errors :\n\n" ); for( ParseException error : errors ) { builder.append( error.getConsoleMessage() ); builder.append( "\n" ); } } builder.append( "\n" ); return builder.toString(); } @Override public String getTypeName() { return _typeName; } public List<ParseWarning> getWarnings() { ArrayList<ParseWarning> warningArrayList = new ArrayList<ParseWarning>(); for( IParseIssue parseIssue : _parseIssues ) { if( parseIssue instanceof ParseWarning ) { warningArrayList.add( (ParseWarning)parseIssue ); } } return warningArrayList; } public List<ParseException> getErrors() { ArrayList<ParseException> warningArrayList = new ArrayList<ParseException>(); for( IParseIssue parseIssue : _parseIssues ) { if( parseIssue instanceof ParseException ) { warningArrayList.add( (ParseException)parseIssue ); } } return warningArrayList; } } private static class ExceptionTypeVerificationResults implements IVerificationResults { private final String _typeName; private final String _msg; public ExceptionTypeVerificationResults( String typeName, String msg ) { _typeName = typeName; _msg = msg; } @Override public boolean containsError() { return true; } @Override public String getFeedback() { return _msg; } @Override public String getTypeName() { return _typeName; } } }