Package etch.compiler

Source Code of etch.compiler.EtchCompiler

/* $Id: EtchCompiler.java 742162 2009-02-08 20:54:34Z dixson $
*
* Copyright 2007-2008 Cisco Systems Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package etch.compiler;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import etch.compiler.ast.Module;
import etch.compiler.ast.Name;
import etch.compiler.ast.Opt;
import etch.compiler.ast.Service;

/**
* The main driver of the compiler.
*/
public class EtchCompiler
{
    /**
     * Instantiate a new compiler
     * @param cl the class loader to use to load the binding
     */
    public EtchCompiler( ClassLoader cl )
    {
      this.cl = cl;
    }
   
    private final ClassLoader cl;
   
    /**
     * Runs the compiler using the specified input options.
     * @param clo
     * @throws Exception
     */
    public void run( CmdLineOptions clo ) throws Exception
    {
      clo.cl = cl;
     
      if (clo.lh == null)
      {
        clo.lh = new ConsoleLogHandler( "Command" );
        clo.lh.setLevel(
          clo.quiet ? LogHandler.LEVEL_WARNING : LogHandler.LEVEL_INFO );
      }
     
      if (!initBindingClass( clo ))
        return;
     
      if (!checkSourceFile( clo ))
        return;
     
      initOutput( clo );
        clo.lh.push( clo.sourceFile.getName(), null );
       
        try
        {
      compile( clo );
       
      if (!clo.lh.hasError())
      {
            clo.lh.report( LogHandler.LEVEL_INFO, "Saving Resources..." );
           
            saveOutput( clo );
           
            if (!clo.lh.hasError())
              clo.lh.report( LogHandler.LEVEL_INFO, "Saving Resources Done." );
            else
              clo.lh.report( LogHandler.LEVEL_INFO, "Saving Resources Failed." );
      }
      else
        clo.lh.report( LogHandler.LEVEL_INFO, "Compile Failed." );
      }
        catch ( Exception e )
        {
          clo.lh.report( LogHandler.LEVEL_ERROR, "caught exception: %s", e );
          e.printStackTrace();
        }
        finally
        {
          clo.lh.pop( clo.sourceFile.getName() );
        }
    }

  private static boolean initBindingClass( CmdLineOptions clo )
  {
    if (clo.bindingClass != null)
      return true;

    String n = clo.binding;
   
    if (n == null || n.length() == 0)
    {
      clo.lh.report( LogHandler.LEVEL_ERROR,
        "Binding not specified." );
      return false;
    }
   
    if (n.equalsIgnoreCase( "help" ))
    {
      // TODO find some way to list the bindings?
      clo.lh.report( LogHandler.LEVEL_ERROR,
        "Binding help not yet implemented." );
      return false;
    }
   
    if (n.equalsIgnoreCase( "null" ))
    {
      clo.bindingClass = NullCompiler.class;
      return true;
    }
   
    String cn = String.format( BINDING_FORMAT, n );
   
    try
    {
      clo.bindingClass = clo.cl.loadClass( cn );
      return true;
    }
    catch ( ClassNotFoundException e )
    {
      clo.lh.report( LogHandler.LEVEL_ERROR,
        "Binding '%s' could not be loaded; class '%s' not in classpath.",
        n, cn );
      return false;
    }
    catch ( RuntimeException e )
    {
      clo.lh.report( LogHandler.LEVEL_ERROR,
        "Binding '%s' could not be loaded; class '%s' threw exception %s",
        n, cn, e );
      e.printStackTrace();
      return false;
    }
  }
 
  private static boolean initBackend( CmdLineOptions clo )
  {
    try
    {
      clo.backend = (Backend) clo.bindingClass.newInstance();
      return true;
    }
    catch ( Exception e )
    {
      clo.lh.report( LogHandler.LEVEL_ERROR,
        "Binding '%s' could not be initialized; caught exception %s.",
        clo.binding, e );
      e.printStackTrace();
      return false;
    }
  }
   
    private final static String BINDING_FORMAT = "etch.bindings.%s.compiler.Compiler";

  private static boolean checkSourceFile( CmdLineOptions clo )
  {
    File f = clo.sourceFile;
   
    if (!f.isFile())
    {
      clo.lh.report( LogHandler.LEVEL_ERROR,
        "Source file '%s' does not exist or is not a file.", f );
      return false;
    }
   
    if (!f.getName().endsWith( ".etch" ))
    {
      clo.lh.report( LogHandler.LEVEL_ERROR,
        "Source file '%s' must have .etch extension.", f );
      return false;
    }
   
    return true;
  }

  /**
     * @param etchHome command line specified location for the etch.
     * @return sets up the class loader based on information from
     * our environment.
     * @throws Exception
     */
    public static ClassLoader setupClassLoader( File etchHome )
        throws Exception
    {
        // get the current class path...
       
        Set<String> mainClassPath = new HashSet<String>();
       
        ClassLoader cl = EtchMain.class.getClassLoader();
        if (cl instanceof URLClassLoader)
        {
            for (URL u: ((URLClassLoader) cl).getURLs())
            {
                String s = u.toString();
                if (s.startsWith( "file:" ) && s.endsWith( ".jar" ))
                {
                    File f = new File( u.toURI() );
                    if (f.isFile() && f.canRead())
                    {
                        s = f.getCanonicalPath();
                        mainClassPath.add( s );
//                      System.out.println( "mainClassPath.add: "+s );
                    }
                }
            }
        }
       
        // search etch.home (if specified) for more jar files to add
        // to a secondary class loader. exclude jar files on the main
        // class path.
       
        String s = etchHome != null ? etchHome.getCanonicalPath() : null;
       
        if (s == null || s.length() == 0)
        {
          // from java -Detch.home=...
            s = System.getProperty( "etch.home" );
        }

        if (s == null || s.length() == 0)
        {
            s = System.getenv( "ETCH_HOME" );
        }
       
        if (s != null && s.length() > 0)
            s = s + File.separator + "lib";

        //      System.out.println( "etch.home.lib = "+s );
        if (s != null && s.length() > 0)
        {
            File d = new File( s );
           
            if (!d.isDirectory() || !d.canRead())
              throw new IOException( String.format(
                "Specified $ETCH_HOME/lib is not a directory: %s\n", s ) );

            d = d.getCanonicalFile();
           
            MyURLClassLoader ncl = new MyURLClassLoader( new URL[] {}, cl );
            for (File f: d.listFiles())
            {
                if (!f.isFile())
                    continue;
                String x = f.getCanonicalPath();
                if (!x.endsWith( ".jar" ))
                    continue;
                if (!mainClassPath.contains( x ))
                    ncl.addURL( f.toURI().toURL() );
            }
            cl = ncl;
        }
        return cl;
    }
   
    private static class MyURLClassLoader extends URLClassLoader
    {
        /**
         * This is just a class loader where we can hang more urls to
         * search.
         * @param urls
         * @param parent
         */
        public MyURLClassLoader( URL[] urls, ClassLoader parent )
        {
            super( urls, parent );
        }
       
        @Override
        public void addURL( URL url )
        {
            super.addURL( url );
        }
    }
   
    // includePath

    /**
     * Initialize {@link CmdLineOptions#effectiveIncludePath} given the
     * compilation we are about to perform.
     * @param clo
     */
    private static void initIncludePath( CmdLineOptions clo ) throws IOException
    {
//      System.out.println( "initIncludePath: sourceFile = "+clo.sourceFile );
//      System.out.println( "initIncludePath: includePath = "+clo.includePath );
     
      // includePath is composed of the source file's directory, then
      // the paths specifies by -I command line directives, then the
      // paths specified by the ETCH_INCLUDE_PATH environment variable.
     
      clo.effectiveIncludePath = new ArrayList<File>( );
     
      // add directory of source file.
      if (clo.sourceFile != null)
        clo.effectiveIncludePath.add(
          clo.sourceFile.getCanonicalFile().getParentFile() );
     
      for (File f: clo.includePath)
      {
        if (f.isDirectory())
          clo.effectiveIncludePath.add( f );
        else
          clo.lh.report( LogHandler.LEVEL_WARNING,
                "Include path element doesn't exist: %s", f );
      }
     
      if (!clo.ignoreIncludePath)
      {
          String s = System.getenv( CmdLineOptions.ETCH_INCLUDE_PATH );
          if (s != null)
          {
              StringTokenizer st = new StringTokenizer( s, File.pathSeparator );
              while (st.hasMoreTokens())
              {
                  File f = new File( st.nextToken() );
                  if (f.isDirectory())
                    clo.effectiveIncludePath.add( f );
                  else
                    clo.lh.report( LogHandler.LEVEL_WARNING,
                      "Environment include path element doesn't exist: %s", f );
              }
          }
      }
//      System.out.println( "initIncludePath: effectiveIncludePath = "+clo.effectiveIncludePath );
    }

  private static Module compile( CmdLineOptions clo ) throws Exception
  {
    InputStream is = new BufferedInputStream( new FileInputStream( clo.sourceFile ) );
      try
    {
      return compile( clo, is );
    }
    finally
    {
      is.close();
    }
  }
   
    private static Module compile( CmdLineOptions clo, InputStream is ) throws Exception
  {
      initIncludePath( clo );
     
      if (!initBackend( clo ))
        return null;
     
      if (clo.sourceFile != null)
        clo.lh.report( LogHandler.LEVEL_INFO, "Compiling %s...", clo.sourceFile );
      else
        clo.lh.report( LogHandler.LEVEL_INFO, "Compiling..." );
     
      final EtchGrammar parser = new EtchGrammar( clo.backend, is );
       
        final Module m;
       
        // Parse .etch source
        try
        {
            m = parser.module( clo );
            clo.lh.report( LogHandler.LEVEL_INFO, "Parsed Ok." );
           
            m.check();
            clo.lh.report( LogHandler.LEVEL_INFO, "Checked Ok." );
        }
        catch ( ParseException e )
        {
            String fmt = e.getMessage();
            clo.lh.report( LogHandler.LEVEL_ERROR,
              e.currentToken != null ? e.currentToken.beginLine : null,
                e.getMessage() );
            // TODO better report of a ParseException
            throw new Exception(e.currentToken + " " + fmt);
        }
        catch ( Exception e )
        {
            clo.lh.report( LogHandler.LEVEL_ERROR, "caught exception: %s", e );
            // TODO better report of an Exception
            throw e;
        }
       
        // Code generation
       
        try
        {
            // TODO report work lists?
          clo.lh.report( LogHandler.LEVEL_INFO, "Generating Resources..." );
           
            // TODO integrate includePath with code generation.
            clo.backend.generate( m, clo );

            // Move generated code to target location
            //   the parser will set 'isMixinPresent' if a "mixin" directive is in the .etch file
            // TODO would be nice if the backend just put things in the right place from the start .. sigh re-factor later       
            // moveGeneratedCode(options.outputDir, options.isMixinPresent, workingDirectory);
        }
        catch ( Exception e )
        {
            String fmt = e.getMessage();
            // TODO better report of an Exception
            e.printStackTrace();
            throw new Exception(fmt);
        }

        // Complete

        clo.lh.report( LogHandler.LEVEL_INFO, "Compile Done." );
        return m;
    }

  private static void initOutput( CmdLineOptions clo )
  {
    clo.output = new FileOutput();
    clo.templateOutput = new FileOutput();
    clo.mixinOutput = clo.noMixinArtifacts ? new NullOutput() : new FileOutput();
  }

  private static void saveOutput( CmdLineOptions clo ) throws IOException
  {
    // TODO implement saveOutput
//    clo.output.report( "output" );
//    clo.templateOutput.report( "templateOutput" );
//    clo.mixinOutput.report( "mixinOutput" );
   
    if (clo.testing)
      return;
   
    // Destination directory of generated artifacts. Use source file's
    // parent directory if not specified.
   
    File outputDir = clo.outputDir;
    boolean noQualOutputDir = false;
    if (outputDir == null)
    {
      outputDir = clo.sourceFile.getCanonicalFile().getParentFile();
      noQualOutputDir = true;
    }

    // Destination directory of generated user-editable artifacts. Use
    // outputDir if not specified.
   
    File templateOutputDir = clo.templateOutputDir;
    boolean noQualTemplateOutputDir = false;
    if (templateOutputDir == null)
    {
      templateOutputDir = outputDir;
      noQualTemplateOutputDir = noQualOutputDir;
    }
   
    // Destination directory of mixin generated artifacts. Use outputDir
    // if not specified.
   
    File mixinOutputDir = clo.mixinOutputDir;
    boolean noQualMixinOutputDir = false;
    if (mixinOutputDir == null)
    {
      mixinOutputDir = outputDir;
      noQualMixinOutputDir = noQualOutputDir;
    }
   
    boolean force = clo.what.contains( Backend.WHAT_FORCE );

    saveFiles( clo.output, outputDir, noQualOutputDir, clo.noFlattenPackages, true );
    saveFiles( clo.templateOutput, templateOutputDir, noQualTemplateOutputDir, clo.noFlattenPackages, force );
    saveFiles( clo.mixinOutput, mixinOutputDir, noQualMixinOutputDir, clo.noFlattenPackages, true );
  }

  private static void saveFiles( Output output, File outputDir,
    boolean noQualOutputDir, boolean noFlattenPackages, boolean force )
    throws IOException
  {
    output.saveFiles( outputDir, noQualOutputDir, noFlattenPackages, force );
  }

  /**
   * Parses a mixin which is within the specified service.
   * @param intf
   * @param n
   * @param opts
   * @return the constructed Mixin object, or null.
   * @throws Exception
   */
  public static Module parseModule( Service intf, Name n, Map<String,
    Opt> opts ) throws Exception
  {
    CmdLineOptions clo = new CmdLineOptions( intf.getCmdLineOptions() );
   
    // find the mixin file
   
    String fn = n.name;
   
    // fn is something like a.b.c.d or perhaps just plain d.
    //
    // we will look for d.etch, a/b/c/d.etch, and a.b.c/d.etch.
    //
    // we can look in the same directory as the original source we are
    // compiling (the working dir), or any of the include path directories.
   
    String module;
    String file;
    int i = fn.lastIndexOf( '.' );
    if (i >= 0)
    {
      module = fn.substring( 0, i );
      file = fn.substring( i+1 );
    }
    else
    {
      module = null;
      file = fn;
    }
   
    file = file + ".etch";
   
//    System.out.printf( "searching for mixin '%s' module '%s'\n", file, module );
//    System.out.println( "includePath = "+clo.includePath );
//    System.out.println( "effectiveIncludePath = "+clo.effectiveIncludePath );
   
    List<File> list = new ArrayList<File>();
   
    // look for the unqualified name
    find( list, clo, null, file );
   
    if (module != null)
    {
      // look for the qualified name
      find( list, clo, module, file );
      find( list, clo, module.replace( '.', '/' ), file );
    }
   
    if (list.isEmpty())
      return null;
   
    File f = list.get( 0 );
//    System.out.println( "mixing in = "+f );
   
    return parseModule0( clo, n, f );
  }
 
  private static void find( List<File> list, CmdLineOptions clo, String path,
    String file ) throws IOException
  {
    for (File d: clo.effectiveIncludePath)
    {
      File f = d;
     
      if (path != null)
        f = new File( f, path );
     
      f = new File( f, file ).getCanonicalFile();
     
      if (f.isFile() && !list.contains( f ))
        list.add( f );
    }
  }

  /**
   * @param clo
   * @param n
   * @param f
   * @return the parsed module of the mixin.
   * @throws Exception
   */
  public static Module parseModule0( CmdLineOptions clo, Name n, File f )
    throws Exception
  {
    clo.sourceFile = f;
    clo.output = clo.mixinOutput;
   
    clo.templateOutput = new NullOutput();
    clo.what.remove( Backend.WHAT_MAIN );
    clo.what.remove( Backend.WHAT_IMPL );
   
    if (clo.noMixinArtifacts)
    {
      clo.what.clear();
      clo.what.add( Backend.WHAT_NONE );
    }
   
    clo.lh.report( LogHandler.LEVEL_INFO, n.token.beginLine,
      "Compiling mixin file: %s", n.name );
    clo.lh.push( f.getName(), n.token.beginLine );
    try
    {
      return compile( clo );
    }
    finally
    {
      clo.lh.pop( f.getName() );
      clo.lh.report( LogHandler.LEVEL_INFO, n.token.beginLine,
        "Done compiling mixin file: %s", n.name );
    }
  }
}
TOP

Related Classes of etch.compiler.EtchCompiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.