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

org.eclipse.emf.codegen.jet.JETCompiler Maven / Gradle / Ivy

/**
 * Copyright (c) 2002-2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.codegen.jet;


import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.eclipse.emf.codegen.CodeGenPlugin;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.URI;


public class JETCompiler implements JETParseEventListener
{
  protected final static char[] NULL_CHAR_ARRAY = {};

  protected String[] templateURIPath;

  protected String templateURI;

  protected JETParser parser;

  protected JETSkeleton skeleton;

  protected JETReader reader;

  protected PrintWriter writer;

  protected List generators = new ArrayList(100);

  protected List constants = new ArrayList(100);

  protected Map constantDictionary = new HashMap(100, 100);

  protected long constantCount = 0;

  /**
   * If true, the newline immediately preceding a scriptlet or directive (though not a successful include directive),
   * along with any intervening spaces, will be stripped from the character data.
   */
  protected boolean fNoNewLineForScriptlets = true;

  protected boolean fUseStaticFinalConstants = true;

  /**
   * If fNoNewLineForScriptlets is true, the trailing newline/space sequence is stripped from each character
   * data segment, and stored in this field. Depending on what follows, it may then be discarded or handled as its
   * own character data segment.
   */
  protected char[] fSavedLine = null;

  /**
   * The depth of the current section, where 0 is outside of any sections. A section is delimited by start and
   * end directives, and must be preceded by an include directive with fail="alternative".
   */
  protected int sectionDepth = 0;

  /**
   * Whether content is currently being skipped. This is set according to skipSections, as sections are started and ended. 
   */
  protected boolean skipping = false;

  /**
   * A stack of sections and whether to start skipping, one from each include with alternative encountered.
   */
  protected Stack skipSections = new Stack();

  /**
   * A skip section entry, records the depth of the section and whether to start skipping there. 
   */
  static class SkipSection
  {
    int depth;
    boolean skip;

    SkipSection(int depth, boolean skip)
    {
      this.depth = depth;
      this.skip = skip;
    }
  }

  protected static final String CONSTANT_PREFIX = "TEXT_";

  public JETCompiler(String templateURI) throws JETException
  {
    this(templateURI, "UTF8");
  }

  public JETCompiler(String templateURI, String encoding) throws JETException
  {
    this(templateURI, openStream(templateURI), encoding);
  }

  public JETCompiler(String templateURI, InputStream inputStream, String encoding) throws JETException
  {
    super();

    this.templateURI = templateURI;
    this.reader = new JETReader(templateURI, inputStream, encoding);
  }

  public JETCompiler(String[] templateURIPath, String relativeTemplateURI) throws JETException
  {
    this(templateURIPath, relativeTemplateURI, "UTF8");
  }

  public JETCompiler(String[] templateURIPath, String relativeTemplateURI, String encoding) throws JETException
  {
    super();

    this.templateURIPath = templateURIPath;
    this.templateURI = relativeTemplateURI;
    String[] actualTemplateURI = findLocation(templateURIPath, 0, relativeTemplateURI);
    this.reader = new JETReader(actualTemplateURI[1], relativeTemplateURI, openStream(actualTemplateURI[0]), encoding);
  }

  public String getResolvedTemplateURI()
  {
    return reader.getFile(0);
  }

  public void handleDirective(String directive, JETMark start, JETMark stop, Map attributes) throws JETException
  {
    if (directive.equals("include"))
    {
      String fileURI = attributes.get("file");
      if (fileURI != null)
      {
        String currentURI = start.getFile();
        String[] resolvedFileURI = resolveLocation(templateURIPath, currentURI, fileURI);
        if (resolvedFileURI[0].equals(currentURI))
        {
          boolean loop = true;
          if (templateURIPath != null)
          {
            String baseURI = start.getBaseURI();
            if (baseURI != null)
            {
              for (int i = 0; i < templateURIPath.length; ++i)
              {
                if (baseURI.equals(templateURIPath[i]))
                {
                  resolvedFileURI = resolveLocation(templateURIPath, i + 1, currentURI, fileURI);
                  loop = false;
                }
              }
            }
          }
          if (loop)
          {
            // Break the cycle.
            //
            return;
          } 
        }
        try
        {
          BufferedInputStream bufferedInputStream = new BufferedInputStream(openStream(resolvedFileURI[1]));
          reader.stackStream(resolvedFileURI[2], resolvedFileURI[0], bufferedInputStream, null);

          // The include succeeded, so if there is an alternative and we're not skipping, we need to start.
          //
          if ("alternative".equals(attributes.get("fail")))
          {
            skipSections.push(new SkipSection(sectionDepth + 1, !skipping));
          }

          // If a newline from the previous character data remains, leave it around to be processed as if it appeared in the included file.
          //
          if (fSavedLine != null)
          {
            return; 
          }
        }
        catch (JETException exception)
        {
          // The include failed, so if there is an alternative, we don't skip it.
          //
          String failType = attributes.get("fail");
          if ("alternative".equals(failType))
          {
            skipSections.push(new SkipSection(sectionDepth + 1, false));
          }
          else if (!"silent".equals(failType))
          {
            throw 
              new JETException
                (CodeGenPlugin.getPlugin().getString
                  ("jet.error.file.cannot.read", 
                   new Object [] { resolvedFileURI[1], start.format("jet.mark.file.line.column") }),
                 exception);
          }
        }
      }
      else
      {
        throw 
          new JETException
            (CodeGenPlugin.getPlugin().getString
              ("jet.error.missing.attribute", 
          new Object []{ "file", start.format("jet.mark.file.line.column") }));
      }
    }
    else if (directive.equals("start"))
    {
      sectionDepth++;
      
      // A section is not allowed without a preceding include with alternative.
      //
      SkipSection skipSection = skipSections.isEmpty() ? null : (SkipSection)skipSections.peek();
      if (skipSection == null || skipSection.depth != sectionDepth)
      {
        throw new JETException
          (CodeGenPlugin.getPlugin().getString
            ("jet.error.section.noinclude",
             new Object[] { start.format("jet.mark.file.line.column") }));
      }
      else if (skipSection.skip)
      {
        skipping = true;
      }
    }
    else if (directive.equals("end"))
    {
      if (sectionDepth == 0)
      {
        throw
          new JETException
            (CodeGenPlugin.getPlugin().getString
              ("jet.error.unmatched.directive",
               new Object[] { "start", "end", start.format("jet.mark.file.line.column") }));
      }
      sectionDepth--;

      // This pop is safe because a section couldn't have been started without an include that pushed.
      //
      if (skipSections.pop().skip)
      {
        skipping = false;
      }
    }
    else if (directive.equals("jet"))
    {
      if (skeleton != null)
      {
        // Multiple jet directives.
      }
      else
      {
        skeleton = new JETSkeleton();
        // Process this first.
        //
        String skeletonURI = attributes.get("skeleton");
        if (skeletonURI != null)
        {
          try
          {
            BufferedInputStream bufferedInputStream = 
              new BufferedInputStream(openStream(resolveLocation(templateURIPath, templateURI, skeletonURI)[1]));
            byte[] input = new byte [bufferedInputStream.available()];
            bufferedInputStream.read(input);
            bufferedInputStream.close();
            String skeletonEncoding = attributes.get("skeletonEncoding");
            skeleton.setCompilationUnitContents(skeletonEncoding == null ? new String(input) : new String(input, skeletonEncoding));
          }
          catch (IOException exception)
          {
            throw new JETException(exception);
          }
        }

        for (Map.Entry entry : attributes.entrySet())
        {
          // Ignore this now
          //
          if (entry.getKey().equals("skeleton"))
          {
            // Ignore
          }
          else if (entry.getKey().equals("package"))
          {
            skeleton.setPackageName(entry.getValue());
          }
          else if (entry.getKey().equals("imports"))
          {
            skeleton.addImports(entry.getValue());
          }
          else if (entry.getKey().equals("class"))
          {
            skeleton.setClassName(entry.getValue());
          }
          else if (entry.getKey().equals("nlString"))
          {
            skeleton.setNLString(entry.getValue());
          }
          else if (entry.getKey().equals("startTag"))
          {
            parser.setStartTag(entry.getValue());
          }
          else if (entry.getKey().equals("endTag"))
          {
            parser.setEndTag(entry.getValue());
          }
          else if (entry.getKey().equals("version"))
          {
            // Ignore the version
          }
          else
          {
            throw 
              new JETException
                (CodeGenPlugin.getPlugin().getString
                  ("jet.error.bad.attribute", 
              new Object []{ entry.getKey(), start.format("jet.mark.file.line.column") }));
          }
        }

        handleNewSkeleton();
      }
    }

    fSavedLine = null;
  }

  protected void handleNewSkeleton()
  {
    // Do nothing
  }

  public void handleExpression(JETMark start, JETMark stop, Map attributes) throws JETException
  {
    if (skipping) return;

    JETGenerator gen = new JETExpressionGenerator(reader.getChars(start, stop));
    addGenerator(gen);
  }

  public void handleScriptlet(JETMark start, JETMark stop, Map attributes) throws JETException
  {
    if (skipping) return;

    fSavedLine = null;
    JETGenerator gen = new JETScriptletGenerator(reader.getChars(start, stop));
    addGenerator(gen);
  }

  public void handleCharData(char[] chars) throws JETException
  {
    if (skipping) return;

    if (fSavedLine != null)
    {
      addCharDataGenerator(fSavedLine);
      fSavedLine = null;
    }

    if (fNoNewLineForScriptlets)
    {
      char[] strippedChars = stripLastNewLineWithBlanks(chars);
      if (strippedChars.length > 0)
      {
        addCharDataGenerator(strippedChars);
      }
    }
    else
    {
      addCharDataGenerator(chars);
    }
  }

  public void addGenerator(JETGenerator gen) throws JETException
  {
    // If a newline from the previous character data remains, add a generator for it.
    //
    if (fSavedLine != null)
    {
      addCharDataGenerator(fSavedLine);
      fSavedLine = null;
    }
    generators.add(gen);
  }

  public void addCharDataGenerator(char[] chars) throws JETException
  {
    // An expression with more that 931 "+" will break Sun and IBM javac compilers.
    //
    if (chars.length > 500)
    {
      int nl = 0;
      int lf = 0;

      int start = 0;
      LOOP:
      for (int i = 0; i < chars.length; ++i)
      {
        switch (chars[i])
        {
          case '\n':
          {
            ++nl;
            break;
          }
          case '\r':
          {
            ++lf;
            break;
          }
          default:
          {
            continue;
          }
        }

        if (lf > 400 || nl > 400)
        {
          for (++i; i < chars.length; ++i)
          {
            switch (chars[i])
            {
              case '\n':
              case '\r':
              {
                continue;
              }
              default:
              {
                int size = i - start;
                char [] block = new char [size];
                System.arraycopy(chars, start, block, 0, size);
                doAddCharDataGenerator(block);
                start = i;
                nl = 0;
                lf = 0;
                continue LOOP;
              }
            }
          }
        }
      }
      if (start != 0)
      {
        int size = chars.length - start;
        char [] block = new char [size];
        System.arraycopy(chars, start, block, 0, size);
        doAddCharDataGenerator(block);
        return;
      }
    }

    doAddCharDataGenerator(chars);
  }

  public void doAddCharDataGenerator(char[] chars) throws JETException
  {
    if (fUseStaticFinalConstants)
    {
      JETConstantDataGenerator gen = constantDictionary.get(chars);
      if (gen == null)
      {
        if (constantCount == 0)
        {
          chars = stripFirstNewLineWithBlanks(chars);
        }
        ++constantCount;
        String label = CONSTANT_PREFIX + constantCount;
        gen = new JETConstantDataGenerator(chars, label);
        constantDictionary.put(chars, gen);
        constants.add(gen);
      }
      generators.add(gen);
    }
    else
    {
      generators.add(new JETCharDataGenerator(chars));
    }
  }

  protected char[] stripFirstNewLineWithBlanks(char[] chars)
  {
    if (chars.length >=  2 && 
          (chars[0] == '\n' && chars[1] == '\r' || chars[0] == '\r' && chars[1] == '\n'))
    {
      chars = new String(chars, 2, chars.length - 2).toCharArray();
    }
    else if (chars.length >= 1 && 
          (chars[0] == '\n' || chars[0] == '\r'))
    {
      chars = new String(chars, 1, chars.length - 1).toCharArray();
    }
    return chars;
  }

  protected char[] stripLastNewLineWithBlanks(char[] chars)
  {
    int i = chars.length - 1;
    while (i > 0 && chars[i] == ' ')
    {
      --i;
    }
    if (chars[i] == '\n')
    {
      if (i > 0 && chars[i - 1] == '\r')
      {
        --i;
      }
      fSavedLine = (new String(chars, i, chars.length - i)).toCharArray();
      if (i == 0)
      {
        return NULL_CHAR_ARRAY;
      }
      else
      {
        chars = new String(chars, 0, i).toCharArray();
        return chars;
      }
    }
    else
    {
      return chars;
    }
  }

  public void beginPageProcessing()
  {
    // Do nothing
  }

  public void endPageProcessing() throws JETException
  {
    if (sectionDepth > 0)
    {
      throw
        new JETException
          (CodeGenPlugin.getPlugin().getString
            ("jet.error.unmatched.directive",
             new Object[] { "end", "start", reader.mark().format("jet.mark.file.line.column") }));
    }

    if (skeleton == null)
    {
      throw
        new JETException
          (CodeGenPlugin.getPlugin().getString
            ("jet.error.missing.jet.directive",
        new Object []{ reader.mark().format("jet.mark.file.line.column") }));
    }

    // If a newline from the previous character data remains, add a generator for it.
    //
    if (fSavedLine != null)
    {
      addCharDataGenerator(fSavedLine);
    }

    List generatedConstants = new ArrayList(constants.size());
    for (JETConstantDataGenerator jetConstantDataGenerator : constants)
    {
      generatedConstants.add(jetConstantDataGenerator.generateConstant());
    }
    skeleton.setConstants(generatedConstants);

    List generatedBody = new ArrayList(generators.size());
    for (JETGenerator jetGenerator : generators)
    {
      generatedBody.add(jetGenerator.generate());
    }
    skeleton.setBody(generatedBody);

    writer.print(skeleton.getCompilationUnitContents());
  }

  public void parse() throws JETException
  {
    // Register our directive.
    //
    JETParser.Directive directive = new JETParser.Directive();
    directive.getDirectives().add("jet");
    directive.getDirectives().add("include");
    directive.getDirectives().add("start");
    directive.getDirectives().add("end");

    JETCoreElement[] coreElements = 
      { 
        directive, 
        new JETParser.QuoteEscape(), 
        new JETParser.Expression(), 
        new JETParser.Scriptlet()
      };

    Class [] accept = 
      { 
        JETParser.Directive.class, 
        JETParser.QuoteEscape.class, 
        JETParser.Expression.class, 
        JETParser.Scriptlet.class 
      };

    parse(coreElements, accept);
  }

  protected void parse(JETCoreElement[] coreElements, Class [] accept) throws JETException
  {
    parser = new JETParser(reader, this, coreElements);
    beginPageProcessing();
    parser.parse(null, accept);
  }

  public void generate(OutputStream oStream) throws JETException
  {
    writer = new PrintWriter(oStream);
    endPageProcessing();
    writer.close();
  }

  public void generate(Writer writer) throws JETException
  {
    this.writer = new PrintWriter(writer);
    endPageProcessing();
    this.writer.close();
  }

  public JETSkeleton getSkeleton()
  {
    return skeleton;
  }

  protected static String[] resolveLocation(String[] templateURIPath, String baseLocationURI, String locationURI)
  {
     return resolveLocation(templateURIPath, 0, baseLocationURI, locationURI);
  }
  
  protected static String[] resolveLocation(String[] templateURIPath, int start, String baseLocationURI, String locationURI)
  {
    String[] result = new String []{ locationURI, locationURI, null};
    URI uri = URI.createURI(locationURI);
    try
    {
      new URL(locationURI);
      uri = CommonPlugin.resolve(uri);
    }
    catch (MalformedURLException exception)
    {
      // Ignore
    }

    if (uri.isRelative() && uri.hasRelativePath())
    {
      String resolvedLocation = "";
      int index = baseLocationURI.lastIndexOf("/");
      if (index != -1)
      {
        resolvedLocation = baseLocationURI.substring(0, index + 1);
      }
      resolvedLocation += uri;
      result[0] = resolvedLocation;
      if (templateURIPath != null)
      {
        String [] location =  findLocation(templateURIPath, start, resolvedLocation);
        resolvedLocation = location[0];
        result[2] = location[1];
      }
      if (resolvedLocation != null)
      {
        result[1] = resolvedLocation;
      }
    }
    
    return result;
  }

  public static String[] findLocation(String[] locationURIPath, int start, String relativeLocationURI)
  {
    String[] result = { null, null};
    for (int i = start; i < locationURIPath.length; ++i)
    {
      result[0] = locationURIPath[i];
      result[1] = locationURIPath[i];

      if (result[0] != null)
      {
        try
        {
          if (!result[0].endsWith("/"))
          {
            result[0] += "/";
          }
          result[0] += relativeLocationURI;
  
          InputStream inputStream = openStream(result[0]);
          inputStream.close();
          break;
        }
        catch (JETException exception)
        {
          result[0] = null;
        }
        catch (IOException exception)
        {
          result[0] = null;
        }
      }
    }
    return result;
  }
  
  public static String find(String[] locationURIPath, String relativeLocationURI)
  {
    return findLocation(locationURIPath, 0, relativeLocationURI)[0];
  }

  public static InputStream openStream(String locationURI) throws JETException
  {
    try
    {
      URI uri = URI.createURI(locationURI);
      URL url;
      try
      {
        uri = CommonPlugin.resolve(uri);
        url = new URL(uri.toString());
      }
      catch (MalformedURLException exception)
      {
        url = new URL("file:" + locationURI);
      }

      BufferedInputStream bufferedInputStream = new BufferedInputStream(url.openStream());
      return bufferedInputStream;
    }
    catch (IOException exception)
    {
      throw new JETException(exception.getLocalizedMessage(), exception);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy