/* * Copyright 2013 Guidewire Software, Inc. */ package gw.lang; 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.launch.*; import gw.lang.mode.GosuMode; import gw.lang.mode.IGosuMode; import gw.lang.mode.RequiresInit; import gw.lang.parser.GosuParserFactory; import gw.lang.parser.IFileContext; import gw.lang.parser.IGosuProgramParser; import gw.lang.parser.IParseIssue; import gw.lang.parser.IParseResult; import gw.lang.parser.ParserOptions; 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.IGosuClass; import gw.lang.reflect.gs.IGosuProgram; import gw.lang.reflect.gs.ITemplateType; import gw.lang.reflect.java.JavaTypes; import gw.util.OSPlatform; import gw.util.StreamUtil; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.Reader; import java.lang.reflect.Method; import java.net.MalformedURLException; 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.ServiceLoader; import java.util.StringTokenizer; public class Gosu implements IGosuLaunch { public static final IStringArgKey ARGKEY_FQN = Launch.factory().createArgKeyBuilder("loads a Gosu program based on a fully qualified name", "FQN") .withLongSwitch("fqn") .hideFromHelp() .build(); public static final IBooleanArgKey ARGKEY_INTERACTIVE = Launch.factory().createArgKeyBuilder("starts an interactive Gosu shell") .withShortSwitch('i') .withLongSwitch("interactive") .build(); public static final IBooleanArgKey ARGKEY_VERIFY = Launch.factory().createArgKeyBuilder("verifies the Gosu source") .withLongSwitch("verify") .build(); public static final IBooleanArgKey ARGKEY_VERSION = Launch.factory().createArgKeyBuilder("displays the version of Gosu") .withLongSwitch("version") .build(); public static final IBooleanArgKey ARGKEY_HELP = Launch.factory().createArgKeyBuilder("displays this command-line help") .withShortSwitch('h') .withLongSwitch("help") .build(); private static List<? extends IArgKey> getArgKeys() { return Arrays.asList( LaunchArgs.FILE_PROGRAM_SOURCE, LaunchArgs.URL_PROGRAM_SOURCE, LaunchArgs.EVAL_PROGRAM_SOURCE, LaunchArgs.CLASSPATH, LaunchArgs.DEFAULT_PROGRAM_FILE, LaunchArgs.USE_TOOLS_JAR, ARGKEY_FQN, ARGKEY_INTERACTIVE, ARGKEY_VERIFY, ARGKEY_VERSION, ARGKEY_HELP ); } private static List<File> _classpath; public static void main( String... args ) { IArgInfo argInfo = Launch.factory().createArgInfo( args ); new Gosu().start( argInfo ); } @Override public int start( IArgInfo argInfo ) { try { String cpValue = argInfo.consumeArg( LaunchArgs.CLASSPATH ); IGosuMode mode = getMode( argInfo ); argInfo.processUnknownArgs(); if (argInfo.getErrorMessage() != null) { System.err.println(argInfo.getErrorMessage()); showHelp(); System.exit(1); } if ( mode.getClass().isAnnotationPresent( RequiresInit.class ) ) { List<File> classpath = makeClasspath(cpValue); if (argInfo.getProgramSource() != null && argInfo.getProgramSource().getFile() != null) { classpath.addAll( initRegistry( argInfo.getProgramSource().getFile() ) ); } init(classpath); } return mode.run(); } catch( Throwable t ) { t.printStackTrace( System.err ); return 2; } } private static List<File> makeClasspath( String cpValue ) { ArrayList<File> cp = new ArrayList<File>(); if ( cpValue != null ) { StringTokenizer st = new StringTokenizer( cpValue, ",", false ); while( st.hasMoreTokens() ) { String s = st.nextToken(); if( ( s.contains( ":" ) && !OSPlatform.isWindows()) || s.contains( ";" ) ) { System.err.println( "WARNING: The Gosu classpath argument should be comma separated to avoid system dependencies.\n" + "It appears you are passing in a system-dependent path delimiter" ); } cp.add(new File(s)); } } 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 iIndex = path.indexOf( "src" + File.separatorChar ); String fqn = path.substring( iIndex + 4 ).replace( File.separatorChar, '.' ); return fqn.substring( 0, fqn.lastIndexOf( '.' ) ); } 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" ); } } public static void setClasspath( List<File> 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( File entry : classpath ) { try { Method addURL = URLClassLoader.class.getDeclaredMethod( "addURL", URL.class ); addURL.setAccessible( true ); addURL.invoke( loader, entry.toURI().toURL() ); } catch( Exception e ) { throw new RuntimeException( e ); } } } reinitGosu( classpath ); TypeSystem.refresh( true ); } private static void reinitGosu( List<File> classpath ) { try { GosuInitialization.instance( TypeSystem.getExecutionEnvironment() ).reinitializeRuntime( 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( null ); } public static void init( List<File> classpath ) { List<File> combined = new ArrayList<File>(); combined.addAll( deriveClasspathFrom( Gosu.class ) ); if( classpath != null ) { combined.addAll( classpath ); } setClasspath(combined); } 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( "shell/registry.xml" ) ); return Collections.emptyList(); } private static boolean isProgramLikeResource(File sourceFile) { return sourceFile != null; } public static boolean bootstrapGosuWhenInitiatedViaClassfile() { if( GosuInitialization.isAnythingInitialized() && GosuInitialization.instance( TypeSystem.getExecutionEnvironment() ).isInitialized() ) { return false; } init(); return true; } 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 ); } } 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 ITemplateType ) { ITemplateGenerator generator = ((ITemplateType) type).getTemplateGenerator(); try { generator.verify(GosuParserFactory.createParser(null)); } catch ( ParseResultsException e) { errors.add( new GosuTypeVerificationResults( typeName.toString(), e.getParseIssues() ) ); } } else if( type instanceof IGosuClass ) { boolean valid = type.isValid(); List<IParseIssue> parseIssues = ((IGosuClass)type).getClassStatement().getParseIssues(); if( parseIssues.size() > 0 && (!valid || includeWarnings) ) { errors.add( new GosuTypeVerificationResults( typeName.toString(), parseIssues ) ); } } } catch( Throwable e ) { errors.add( new ExceptionTypeVerificationResults( typeName.toString(), e.getMessage() ) ); } } static void showHelp() { showHelp(new PrintWriter(StreamUtil.getOutputStreamWriter(System.out))); } static void showHelp(PrintWriter out) { out.println("Usage:"); out.println(" gosu [options] [program [args...]]" ); out.println(); out.println("Options:"); IArgKeyList keys = Launch.factory().createArgKeyList(); for (IArgKey key : getArgKeys()) { keys.register(key); } keys.printHelp(out); } 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 GosuVersion getVersion() { InputStream in = Gosu.class.getClassLoader().getResourceAsStream(GosuVersion.RESOURCE_PATH); Reader reader = StreamUtil.getInputStreamReader(in); return GosuVersion.parse(reader); } public interface IVerificationResults { 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 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 String getFeedback() { return _msg; } @Override public String getTypeName() { return _typeName; } } static IGosuMode getMode(IArgInfo argInfo) { List<IGosuMode> modes = new ArrayList<IGosuMode>(); modes.add(new VersionMode()); modes.add(new HelpMode()); modes.add(new VerifyMode()); modes.add(new ExecuteMode()); ServiceLoader<IGosuMode> gosuModeLoader = ServiceLoader.load(IGosuMode.class); for (IGosuMode mode : gosuModeLoader) { modes.add(mode); } Collections.sort(modes); IGosuMode selectedMode = null; for (IGosuMode mode : modes) { mode.setArgInfo(argInfo); if (mode.accept()) { selectedMode = mode; break; } } if (selectedMode == null) { selectedMode = modes.get(0); } return selectedMode; } static class VersionMode extends GosuMode { @Override public int getPriority() { return GOSU_MODE_PRIORITY_VERSION; } @Override public boolean accept() { return _argInfo.consumeArg(ARGKEY_VERSION); } @Override public int run() { System.out.println(getVersion()); return 0; } } static class HelpMode extends GosuMode { @Override public int getPriority() { return GOSU_MODE_PRIORITY_HELP; } @Override public boolean accept() { return _argInfo.consumeArg(ARGKEY_HELP); } @Override public int run() { showHelp(); return 0; } } @RequiresInit static class VerifyMode extends GosuMode { @Override public int getPriority() { return GOSU_MODE_PRIORITY_VERIFY; } @Override public boolean accept() { return _argInfo.consumeArg(ARGKEY_VERIFY); } @Override public int run() { printVerificationResults(); return 0; } } @RequiresInit public static class ExecuteMode extends GosuMode { private String _fqn = null; @Override public int getPriority() { return GOSU_MODE_PRIORITY_EXECUTE; } @Override public boolean accept() { _fqn = _argInfo.consumeArg(ARGKEY_FQN); return (_fqn != null && !_fqn.isEmpty()) || _argInfo.getProgramSource() != null; } public List<String> getProgramArgs() { return _argInfo.getArgsList(); } public String getFqn() { return _fqn; } @Override public int run() throws Exception { if( _fqn != null && !_fqn.isEmpty() ) { return runWithType(); } else { return runWithFile(); } } private int runWithType() throws IOException, ParseResultsException { CommandLineAccess.setRawArgs( _argInfo.getArgsList() ); IGosuProgram program = (IGosuProgram)TypeSystem.getByFullName( _fqn ); program.getProgramInstance().evaluate( null ); return 0; } private int runWithFile() throws IOException, ParseResultsException { CommandLineAccess.setCurrentProgram( _argInfo.getProgramSource().getFile() ); // set remaining arguments as arguments to the Gosu program CommandLineAccess.setRawArgs( _argInfo.getArgsList() ); byte[] bytes = StreamUtil.getContent( _argInfo.getProgramSource().openInputStream() ); String content = StreamUtil.toString( bytes ); IFileContext ctx; if( _argInfo.getProgramSource().getFile() != null ) { File file = _argInfo.getProgramSource().getFile(); ctx = new ProgramFileContext( file, _fqn != null ? _fqn : makeFqn( file ) ); } else { ctx = new IFileContext() { public String getClassName() { return _fqn != null ? _fqn : "program.TestProgram"; } public String getFilePath() { return "TestProgram.gsp"; } public String getContextString() { return null; } }; } 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 ); } return 0; } } }