Package com.google.gwt.dev.shell

Source Code of com.google.gwt.dev.shell.StandardGeneratorContext$PendingResource

/*
* Copyright 2007 Google 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 com.google.gwt.dev.shell;

import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.jdt.CacheManager;
import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
import com.google.gwt.dev.jdt.TypeOracleBuilder;
import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
import com.google.gwt.dev.util.Util;

import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* An abstract implementation of a generator context in terms of a
* {@link com.google.gwt.dev.jdt.MutableCompilationServiceHost}, a
* {@link com.google.gwt.dev.jdt.PropertyOracle}, and a
* {@link com.google.gwt.core.server.typeinfo.TypeOracle}. The generator
* interacts with the mutable source oracle by increasing the available
* compilation units as they are generated.
*/
public class StandardGeneratorContext implements GeneratorContext {

  /**
   * This compilation unit provider acts as a normal compilation unit provider
   * as well as a buffer into which generators can write their source. A
   * controller should ensure that source isn't requested until the generator
   * has finished writing it.
   */
  private static class GeneratedCompilationUnitProvider extends
      StaticCompilationUnitProvider {

    public CharArrayWriter caw;

    public PrintWriter pw;

    public char[] source;

    public GeneratedCompilationUnitProvider(String packageName,
        String simpleTypeName) {
      super(packageName, simpleTypeName, null);
      caw = new CharArrayWriter();
      pw = new PrintWriter(caw, true);
    }

    /**
     * Finalizes the source and adds this compilation unit to the host.
     */
    public void commit() {
      source = caw.toCharArray();
      pw.close();
      pw = null;
      caw.close();
      caw = null;
    }

    public char[] getSource() {
      if (source == null) {
        throw new IllegalStateException("source not committed");
      }
      return source;
    }
  }

  /**
   * {@link CompilationUnitProvider} used to represent generated source code
   * which is stored on disk.  This class is only used if the -gen flag is
   * specified.
   */
  private static final class GeneratedCUP extends URLCompilationUnitProvider {
    private GeneratedCUP(URL url, String name) {
      super(url, name);
    }

    public long getLastModified() throws UnableToCompleteException {
      // Make it seem really old so it won't cause recompiles.
      //
      return 0L;
    }
   
    public boolean isTransient() {
      return true;
    }
  }

  /**
   * Manages a resource that is in the process of being created by a generator.
   */
  private static class PendingResource {

    private final File pendingFile;
    private final ByteArrayOutputStream baos = new ByteArrayOutputStream();

    public PendingResource(File pendingFile) {
      this.pendingFile = pendingFile;
    }

    public void commit(TreeLogger logger) throws UnableToCompleteException {
      logger = logger.branch(TreeLogger.TRACE, "Writing generated resource '"
          + pendingFile.getAbsolutePath() + "'", null);

      if (pendingFile.exists()) {
        logger.log(TreeLogger.ERROR,
            "The destination file already exists; aborting the commit", null);
        throw new UnableToCompleteException();
      }

      Util.writeBytesToFile(logger, pendingFile, baos.toByteArray());
    }

    public File getFile() {
      return pendingFile;
    }

    public OutputStream getOutputStream() {
      return baos;
    }

    public boolean isSamePath(TreeLogger logger, File other) {
      File failedFile = null;
      try {
        /*
         * We try to convert both files to their canonical form. Either one
         * might throw an exception, so we keep track of the one being converted
         * so that we can say which one failed in the case of an error.
         */
        failedFile = pendingFile;
        File thisFile = pendingFile.getCanonicalFile();
        failedFile = pendingFile;
        File thatFile = other.getCanonicalFile();

        if (thisFile.equals(thatFile)) {
          return true;
        } else {
          return false;
        }
      } catch (IOException e) {
        logger.log(TreeLogger.ERROR,
            "Unable to determine canonical path of pending resource '"
                + failedFile.toString() + "'", e);
      }
      return false;
    }
  }

  private final CacheManager cacheManager;

  private final Set committedGeneratedCups = new HashSet();

  private final File genDir;

  private final Set generatedTypeNames = new HashSet();

  private final File outDir;

  private final Map pendingResourcesByOutputStream = new IdentityHashMap();

  private final PropertyOracle propOracle;

  private final TypeOracle typeOracle;

  private final Map uncommittedGeneratedCupsByPrintWriter = new IdentityHashMap();

  /**
   * Normally, the compiler host would be aware of the same types that are
   * available in the supplied type oracle although it isn't strictly required.
   */
  public StandardGeneratorContext(TypeOracle typeOracle,
      PropertyOracle propOracle, File genDir, File outDir,
      CacheManager cacheManager) {
    this.propOracle = propOracle;
    this.typeOracle = typeOracle;
    this.genDir = genDir;
    this.outDir = outDir;
    this.cacheManager = cacheManager;
  }

  /**
   * Commits a pending generated type.
   */
  public final void commit(TreeLogger logger, PrintWriter pw) {
    GeneratedCompilationUnitProvider gcup = (GeneratedCompilationUnitProvider) uncommittedGeneratedCupsByPrintWriter.get(pw);
    if (gcup != null) {
      gcup.commit();
      uncommittedGeneratedCupsByPrintWriter.remove(pw);
      committedGeneratedCups.add(gcup);
    } else {
      logger.log(TreeLogger.WARN,
          "Generator attempted to commit an unknown PrintWriter", null);
    }
  }

  public void commitResource(TreeLogger logger, OutputStream os)
      throws UnableToCompleteException {

    // Find the pending resource using its output stream as a key.
    PendingResource pendingResource = (PendingResource) pendingResourcesByOutputStream.get(os);
    if (pendingResource != null) {
      // Actually write the bytes to disk.
      pendingResource.commit(logger);

      // The resource is now no longer pending, so remove it from the map.
      // If the commit above throws an exception, it's okay to leave the entry
      // in the map because it will be reported later as not having been
      // committed, which is accurate.
      pendingResourcesByOutputStream.remove(os);
    } else {
      logger.log(TreeLogger.WARN,
          "Generator attempted to commit an unknown OutputStream", null);
      throw new UnableToCompleteException();
    }
  }

  /**
   * Call this whenever generators are known to not be running to clear out
   * uncommitted compilation units and to force committed compilation units to
   * be parsed and added to the type oracle.
   *
   * @return types generated during this object's lifetime
   */
  public final JClassType[] finish(TreeLogger logger)
      throws UnableToCompleteException {

    abortUncommittedResources(logger);

    // Process pending generated types.
    List genTypeNames = new ArrayList();

    try {
      TreeLogger branch;
      if (!committedGeneratedCups.isEmpty()) {
        // Assimilate the new types into the type oracle.
        //
        String msg = "Assimilating generated source";
        branch = logger.branch(TreeLogger.DEBUG, msg, null);

        TreeLogger subBranch = null;
        if (branch.isLoggable(TreeLogger.DEBUG)) {
          subBranch = branch.branch(TreeLogger.DEBUG,
              "Generated source files...", null);
        }

        assert (cacheManager.getTypeOracle() == typeOracle);
        TypeOracleBuilder builder = new TypeOracleBuilder(cacheManager);
        for (Iterator iter = committedGeneratedCups.iterator(); iter.hasNext();) {
          GeneratedCompilationUnitProvider gcup = (GeneratedCompilationUnitProvider) iter.next();
          String typeName = gcup.getTypeName();
          String genTypeName = gcup.getPackageName() + "." + typeName;
          genTypeNames.add(genTypeName);
          CompilationUnitProvider cup = writeSource(logger, gcup, typeName);
          builder.addCompilationUnit(cup);
          cacheManager.addGeneratedCup(cup);
         
          if (subBranch != null) {
            subBranch.log(TreeLogger.DEBUG, cup.getLocation(), null);
          }
        }
       
        builder.build(branch);
      }

      // Return the generated types.
      JClassType[] genTypes = new JClassType[genTypeNames.size()];
      int next = 0;
      for (Iterator iter = genTypeNames.iterator(); iter.hasNext();) {
        String genTypeName = (String) iter.next();
        try {
          genTypes[next++] = typeOracle.getType(genTypeName);
        } catch (NotFoundException e) {
          String msg = "Unable to find recently-generated type '" + genTypeName;
          logger.log(TreeLogger.ERROR, msg, null);
          throw new UnableToCompleteException();
        }
      }
      return genTypes;
    } finally {

      // Remind the user if there uncommitted cups.
      if (!uncommittedGeneratedCupsByPrintWriter.isEmpty()) {
        String msg = "For the following type(s), generated source was never committed (did you forget to call commit()?)";
        logger = logger.branch(TreeLogger.WARN, msg, null);

        for (Iterator iter = uncommittedGeneratedCupsByPrintWriter.values().iterator(); iter.hasNext();) {
          StaticCompilationUnitProvider cup = (StaticCompilationUnitProvider) iter.next();
          String typeName = cup.getPackageName() + "." + cup.getTypeName();
          logger.log(TreeLogger.WARN, typeName, null);
        }
      }

      uncommittedGeneratedCupsByPrintWriter.clear();
      committedGeneratedCups.clear();
      generatedTypeNames.clear();
    }
  }

  public File getOutputDir() {
    return outDir;
  }

  public final PropertyOracle getPropertyOracle() {
    return propOracle;
  }

  public final TypeOracle getTypeOracle() {
    return typeOracle;
  }

  public final PrintWriter tryCreate(TreeLogger logger, String packageName,
      String simpleTypeName) {
    String typeName = packageName + "." + simpleTypeName;

    // Is type already known to the host?
    JClassType existingType = typeOracle.findType(packageName, simpleTypeName);
    if (existingType != null) {
      logger.log(TreeLogger.DEBUG, "Type '" + typeName
          + "' already exists and will not be re-created ", null);
      return null;
    }

    // Has anybody tried to create this type during this iteration?
    if (generatedTypeNames.contains(typeName)) {
      final String msg = "A request to create type '"
          + typeName
          + "' was received while the type itself was being created; this might be a generator or configuration bug";
      logger.log(TreeLogger.WARN, msg, null);
      return null;
    }

    // The type isn't there, so we can let the caller create it. Remember that
    // it is pending so another attempt to create the same type will fail.
    GeneratedCompilationUnitProvider gcup = new GeneratedCompilationUnitProvider(
        packageName, simpleTypeName);
    uncommittedGeneratedCupsByPrintWriter.put(gcup.pw, gcup);
    generatedTypeNames.add(typeName);

    return gcup.pw;
  }

  public OutputStream tryCreateResource(TreeLogger logger, String name)
      throws UnableToCompleteException {

    logger = logger.branch(TreeLogger.DEBUG,
        "Preparing pending output resource '" + name + "'", null);

    // Disallow null or empty names.
    if (name == null || name.trim().equals("")) {
      logger.log(TreeLogger.ERROR,
          "The resource name must be a non-empty string", null);
      throw new UnableToCompleteException();
    }

    // Disallow absolute paths.
    File f = new File(name);
    if (f.isAbsolute()) {
      logger.log(
          TreeLogger.ERROR,
          "Resource paths are intended to be relative to the compiled output directory and cannot be absolute",
          null);
      throw new UnableToCompleteException();
    }

    // Disallow backslashes (to promote consistency in calling code).
    if (name.indexOf('\\') >= 0) {
      logger.log(
          TreeLogger.ERROR,
          "Resource paths must contain forward slashes (not backslashes) to denote subdirectories",
          null);
      throw new UnableToCompleteException();
    }

    // Compute the final path.
    File pendingFile = new File(outDir, name);

    // See if this file is already pending.
    for (Iterator iter = pendingResourcesByOutputStream.values().iterator(); iter.hasNext();) {
      PendingResource pendingResource = (PendingResource) iter.next();
      if (pendingResource.isSamePath(logger, pendingFile)) {
        // It is already pending.
        logger.log(TreeLogger.WARN, "The file is already a pending resource",
            null);
        return null;
      }
    }

    // If this file already exists, we won't overwrite it.
    if (pendingFile.exists()) {
      logger.log(TreeLogger.TRACE, "File already exists", null);
      return null;
    }

    // Record that this file is pending.
    PendingResource pendingResource = new PendingResource(pendingFile);
    OutputStream os = pendingResource.getOutputStream();
    pendingResourcesByOutputStream.put(os, pendingResource);

    return os;
  }

  private void abortUncommittedResources(TreeLogger logger) {
    if (pendingResourcesByOutputStream.isEmpty()) {
      // Nothing to do.
      return;
    }

    // Warn the user about uncommitted resources.
    logger = logger.branch(
        TreeLogger.WARN,
        "The following resources will not be created because they were never committed (did you forget to call commit()?)",
        null);

    try {
      for (Iterator iter = pendingResourcesByOutputStream.values().iterator(); iter.hasNext();) {
        PendingResource pendingResource = (PendingResource) iter.next();
        logger.log(TreeLogger.WARN,
            pendingResource.getFile().getAbsolutePath(), null);
      }
    } finally {
      pendingResourcesByOutputStream.clear();
    }
  }

  /**
   * Writes the source of the specified compilation unit to disk if a gen
   * directory is specified.
   *
   * @param cup the compilation unit whose contents might need to be written
   * @param simpleTypeName the fully-qualified type name
   * @return a wrapper for the existing cup with a proper location
   */
  private CompilationUnitProvider writeSource(TreeLogger logger,
      CompilationUnitProvider cup, String simpleTypeName)
      throws UnableToCompleteException {

    if (genDir == null) {
      // No place to write it.
      return cup;
    }

    if (Util.isCompilationUnitOnDisk(cup.getLocation())) {
      // Already on disk.
      return cup;
    }

    // Let's do write it.
    String typeName = cup.getPackageName() + "." + simpleTypeName;
    String relativePath = typeName.replace('.', '/') + ".java";
    File srcFile = new File(genDir, relativePath);
    Util.writeCharsAsFile(logger, srcFile, cup.getSource());

    // Update the location of the cup
    Throwable caught = null;
    try {
      URL fileURL = srcFile.toURL();
      URLCompilationUnitProvider fileBaseCup = new GeneratedCUP(fileURL,
          cup.getPackageName());
      return fileBaseCup;
    } catch (MalformedURLException e) {
      caught = e;
    }
    logger.log(TreeLogger.ERROR,
        "Internal error: cannot build URL from synthesized file name '"
            + srcFile.getAbsolutePath() + "'", caught);
    throw new UnableToCompleteException();
  }
}
TOP

Related Classes of com.google.gwt.dev.shell.StandardGeneratorContext$PendingResource

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.