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

org.coursera.courier.api.DefaultGeneratorRunner Maven / Gradle / Ivy

/*
 * Copyright 2015 Coursera 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 org.coursera.courier.api;

import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.DataSchemaLocation;
import com.linkedin.data.schema.resolver.FileDataSchemaLocation;
import com.linkedin.pegasus.generator.CodeUtil;
import com.linkedin.pegasus.generator.DataSchemaParser;
import com.linkedin.pegasus.generator.DefaultGeneratorResult;
import com.linkedin.pegasus.generator.GeneratorResult;
import com.linkedin.pegasus.generator.spec.ClassTemplateSpec;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Runs a {@link org.coursera.courier.api.PegasusCodeGenerator} for all the .pdsc files in a
 * project.
 */
public class DefaultGeneratorRunner implements GeneratorRunner {
  @Override
  public GeneratorResult run(
      PegasusCodeGenerator generator,
      GeneratorRunnerOptions options) throws IOException {

    MultiFormatSchemaParser schemaParser =
        new MultiFormatSchemaParser(options.getResolverPath(), options.getParsersForFileFormats());

    CourierTemplateSpecGenerator specGenerator = new CourierTemplateSpecGenerator(
        schemaParser.getSchemaResolver(),
        options.getDataNamespace(),
        generator.customTypeLanguage());

    File targetDirectory = new File(options.getTargetDirectoryPath());

    try {
      FileUtils.forceMkdir(targetDirectory);
    } catch (IOException e) {
      throw new IOException(
          "Unable to create targetDirectory, or directory path exists but is not a directory: " +
              targetDirectory.getAbsolutePath(), e);
    }

    for (DataSchema defined: generator.definedSchemas()) {
      specGenerator.registerDefinedSchema(defined);
    }

    DataSchemaParser.ParseResult parseResult = schemaParser.parseSources(options.getSources());

    for (Map.Entry entry: parseResult.getSchemaAndLocations().entrySet()) {
      specGenerator.generate(entry.getKey(), entry.getValue());
    }

    // Build a set of top level types so that we only generate each class file exactly once
    // and so that we don't accidentally stack overflow if types are recursively defined.
    Set topLevelSpecs = new HashSet();
    for(ClassTemplateSpec generatedSpec: specGenerator.getGeneratedSpecs()) {
      topLevelSpecs.addAll(findTopLevelTypes(generatedSpec));
    }

    // Run the generator.
    Collection generated;
    if (options.isGeneratePredef()) {
      generated = generator.generatePredef();
    } else {
      generated = new HashSet();
      for (ClassTemplateSpec topLevel : topLevelSpecs) {
        GeneratedCode generatedForTopLevel = generator.generate(topLevel);
        if (generatedForTopLevel != null) {
          generated.add(generatedForTopLevel);
        }
      }
    }

    // Write the resulting files.
    Collection targetFiles = new HashSet();
    for (GeneratedCode result: generated) {
      targetFiles.add(writeCode(targetDirectory, result));
    }

    // Delete any unrecognized files from target directory.
    try {
      deleteUnrecognizedFiles(targetDirectory, targetFiles);
    } catch (IOException e) {
      throw new IOException(
          "Unexpected error while clearing unused files from targetDirectory:" +
              targetDirectory.getAbsolutePath(), e);
    }


    // CourierPlugin.prepareCacheUpdate checks if the generator needs to run using an SBT utility,
    // so if we get here we know we should unconditionally run the generator. As a result, the
    // modifiedFiles are always the same as the target files. (if we instead, used
    // FileUtils.upToDate here to do the check, modifiedFiles might be empty if all files are
    // upToDate).
    Collection modifiedFiles = targetFiles;

    return new DefaultGeneratorResult(
        parseResult.getSourceFiles(),
        targetFiles,
        modifiedFiles);
  }

  @SuppressWarnings("unchecked")
  private void deleteUnrecognizedFiles(
      File targetDirectory, Collection targetFiles) throws IOException {
    Collection existingFiles =
        (Collection)FileUtils.listFiles(targetDirectory, null, true);

    for (File existingFile : existingFiles) {
      if (!targetFiles.contains(existingFile)) {
        FileUtils.forceDelete(existingFile);
      }
    }
  }


  /**
   * Currently, one ClassDefinition is provided per .pdsc file. But some of those .pdsc contain
   * inline schema definitions that should be generated into top level classes.
   *
   * This method traverses the spec hierarchy, finding all specs that should be generated as top
   * level classes.
   *
   * I've asked the rest.li team to consider restructuring the generator utilities so that one
   * ClassDefinition per top level class is provided. If they restructure the utilities, this
   * method should no longer be needed.
   */
  private static Set findTopLevelTypes(ClassTemplateSpec spec) {
    Set specs =
        new HashSet(ClassTemplateSpecs.allReferencedTypes(spec));
    specs.add(spec);

    Iterator iterator = specs.iterator();
    while (iterator.hasNext()) {
      ClassTemplateSpec entry = iterator.next();
      if (entry.getEnclosingClass() != null) {
        iterator.remove();
      }
    }
    return specs;
  }

  private static File writeCode(
      File targetDirectory, GeneratedCode generated) throws IOException {
    GeneratedCodeTargetFile target = generated.getTarget();
    File file = target.toFile(targetDirectory);
    File directory = file.getParentFile();
    directory.mkdirs();
    if (!directory.exists() || !directory.isDirectory()) {
      throw new IllegalArgumentException(
          "unable to create directory, or directory path exists but is not a directory: " +
              directory.getAbsolutePath());
    }

    if (!file.exists()) {
      if (!file.createNewFile()) {
        throw new IllegalArgumentException("unable to create file: " + file.getAbsolutePath());
      }
    }

    PrintWriter stream = new PrintWriter(new FileOutputStream(file));
    try {
      stream.write(generated.getCode());
    } finally {
      stream.close();
    }
    return file;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy