content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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
   *
   *     https://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.apache.avro.mojo;
  
  import java.io.File;
  import java.io.PrintWriter;
  import java.lang.reflect.Constructor;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.nio.charset.Charset;
  import java.util.List;
  
  import org.apache.avro.AvroRuntimeException;
  import org.apache.avro.reflect.ReflectData;
  import org.apache.maven.plugin.AbstractMojo;
  import org.apache.maven.plugin.MojoExecutionException;
  import org.apache.maven.project.MavenProject;
  
  /**
   * Generate Avro files (.avsc and .avpr) from Java classes or interfaces
   *
   * @goal induce
   * @phase process-classes
   * @threadSafe
   */
  public class InduceMojo extends AbstractMojo {
    /**
     * The Java source directories.
     *
     * @parameter property="javaSourceDirectories"
     *            default-value="${basedir}/src/main/java"
     */
    private File[] javaSourceDirectories;
  
    /**
     * Directory where to output Avro schemas (.avsc) or protocols (.avpr).
     *
     * @parameter property="avroOutputDirectory"
     *            default-value="${project.build.directory}/generated-resources/avro"
     */
    private File avroOutputDirectory;
  
    /**
     * The output encoding.
     *
     * @parameter default-value="${project.build.sourceEncoding}"
     */
    private String encoding;
  
    /**
     * Whether to use ReflectData.AllowNull.
     *
     * @parameter default-value="false"
     */
    private boolean allowNull;
  
    /**
     * Override the default ReflectData implementation with an extension. Must be a
     * subclass of ReflectData.
     *
     * @parameter property="reflectDataImplementation"
     */
    private String reflectDataImplementation;
  
    /**
     * The current Maven project.
     *
     * @parameter default-value="${project}"
     * @readonly
     * @required
     */
    protected MavenProject project;
  
    private ClassLoader classLoader;
    private ReflectData reflectData;
  
    public void execute() throws MojoExecutionException {
      classLoader = getClassLoader();
      reflectData = getReflectData();
  
      if (encoding == null) {
        encoding = Charset.defaultCharset().name();
        getLog().warn("Property project.build.sourceEncoding not set, using system default " + encoding);
      }
  
      for (File sourceDirectory : javaSourceDirectories) {
        induceClasses(sourceDirectory);
      }
    }
  
    private void induceClasses(File sourceDirectory) throws MojoExecutionException {
      File[] files = sourceDirectory.listFiles();
      if (files == null) {
        throw new MojoExecutionException("Unable to list files from directory: " + sourceDirectory.getName());
      }
  
      for (File inputFile : files) {
        if (inputFile.isDirectory()) {
          induceClasses(inputFile);
          continue;
        }
  
        String className = parseClassName(inputFile.getPath());
        if (className == null) {
          continue; // Not a java file, continue
        }
  
        Class<?> klass = loadClass(classLoader, className);
        String fileName = getOutputFileName(klass);
        File outputFile = new File(fileName);
        outputFile.getParentFile().mkdirs();
        try (PrintWriter writer = new PrintWriter(fileName, encoding)) {
          if (klass.isInterface()) {
            writer.println(reflectData.getProtocol(klass).toString(true));
          } else {
            writer.println(reflectData.getSchema(klass).toString(true));
          }
        } catch (AvroRuntimeException e) {
          throw new MojoExecutionException("Failed to resolve schema or protocol for class " + klass.getCanonicalName(),
              e);
        } catch (Exception e) {
          throw new MojoExecutionException("Failed to write output file for class " + klass.getCanonicalName(), e);
        }
      }
    }
  
    private String parseClassName(String fileName) {
      String indentifier = "java" + File.separator;
      int index = fileName.lastIndexOf(indentifier);
      String namespacedFileName = fileName.substring(index + indentifier.length());
      if (!namespacedFileName.endsWith(".java")) {
        return null;
      }
  
      return namespacedFileName.replace(File.separator, ".").replaceFirst("\\.java$", "");
    }
  
    private String getOutputFileName(Class klass) {
      String filename = avroOutputDirectory.getPath() + File.separator + klass.getName().replace(".", File.separator);
      if (klass.isInterface()) {
        return filename.concat(".avpr");
      } else {
        return filename.concat(".avsc");
      }
    }
  
    private ReflectData getReflectData() throws MojoExecutionException {
      if (reflectDataImplementation == null) {
        return allowNull ? ReflectData.AllowNull.get() : ReflectData.get();
      }
  
      try {
        Constructor<? extends ReflectData> constructor = loadClass(classLoader, reflectDataImplementation)
            .asSubclass(ReflectData.class).getConstructor();
        constructor.setAccessible(true);
        return constructor.newInstance();
      } catch (Exception e) {
        throw new MojoExecutionException(String.format(
            "Could not load ReflectData custom implementation %s. Make sure that it has a no-args constructor",
            reflectDataImplementation), e);
      }
    }
  
    private Class<?> loadClass(ClassLoader classLoader, String className) throws MojoExecutionException {
      try {
        return classLoader.loadClass(className);
      } catch (ClassNotFoundException e) {
        throw new MojoExecutionException("Failed to load class " + className, e);
      }
    }
  
    private ClassLoader getClassLoader() throws MojoExecutionException {
      ClassLoader classLoader;
  
      try {
        List<String> classpathElements = project.getRuntimeClasspathElements();
        if (null == classpathElements) {
          return Thread.currentThread().getContextClassLoader();
        }
        URL[] urls = new URL[classpathElements.size()];
  
        for (int i = 0; i < classpathElements.size(); ++i) {
          urls[i] = new File(classpathElements.get(i)).toURI().toURL();
        }
        classLoader = new URLClassLoader(urls, getClass().getClassLoader());
      } catch (Exception e) {
        throw new MojoExecutionException("Failed to obtain ClassLoader", e);
      }
  
      return classLoader;
    }
  }
  