<commit_before>package com.github.venkateshamurthy.designpatterns.builders;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.CLASS_NAMES;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.FILE_LISTING;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.HELP;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.SET_METHOD_PATTERN;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.SRC_FOLDER;

import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.fuin.srcgen4javassist.SgClass;
import org.fuin.srcgen4javassist.SgClassPool;

import com.fluentinterface.ReflectionBuilder;
import com.fluentinterface.builder.Builder;

/** A Simple utility class to create Fluent Builders for a reasonably complex objects(POJOs) that are usually auto-generated with void returning set
 * methods. These Builders could come in handy when the number of properties in a POJO quickly run out of hand (say >4).
 * <p>
 * It basically creates an interface by capturing and adding the typical setter methods of an object (typically POJO) with an intent to proxy-wire the
 * builder interface to the pojo class
 * <p>
 * This class makes use of the following open source libraries to achieve the objective for which the author acknowledges and thanks by mentioning it
 * here.
 * <ol>
 * <li><a href="http://www.javassist.org"><b><i>javassist</i></b> - to create interface object dynamically</a></li>
 * <li><a href="http://www.fuin.org/srcgen4javassist/index.html"><i><b> srcgen4javassist</b></i> - to write the interface source in human readable
 * source code format</a></li>
 * <li><a href="https://github.com/davidmarquis/fluent-interface-proxy"><b><i> fluentinterface</i></b> - for actually generating the proxy builder for
 * the pojo using the interface source file</a></li>
 * </ol>
 * <p>
 * <b>How to use for creating the builder(usually during the maven source generation phase):</b> <br>
 * <br>
 * <code>
 * FluentBuilders builders={@link #create() FluentBuilders.create()};<br>
 * Class[] classes= ...(List of pojo class objects);<br>
 * {@link #writeInterface(Class...) builders.writeInterface(classes)};
 * </code>
 * <p>
 * <b>How to use the auto-generated builder for pojo:</b><br>
 * <br>
 * Assume the following: <ll>
 * <li>A POJO class called ErrorInfo exists that has few properties including <code>internalErrorCode</code> and <code>serviceErrorCode</code>.
 * <li>An ErrorInfoBuilder for building ErrorInfo is auto-generated during source generation. </ll> <br>
 * <br>
 * Now,in order to create Builder and use:<br>
 * <br>
 * <code>
 * final ErrorInfoBuilder errBuilder = FluentBuilders.builder(ErrorInfo.class);<br>
 * final ErrorInfo errInfo =
 * errBuilder.setInternalErrorCode(1234L).setServiceErrorCode(2134L).build();
 * </code>
 * 
 * @author <EMAIL> */
public class FluentBuilders {
    /** Command options enum*/
    protected static enum CMD_OPTIONS{
        FILE_LISTING("file","file-listing"),
        CLASS_NAMES("cls","classes"),
        SRC_FOLDER("src","src-folder-name"),
        SET_METHOD_PATTERN("set","set-method-pattern"),
        HELP("h","help");
        private final String shorter;
        private final String longer;
        CMD_OPTIONS(String shorter,String longer){
            this.shorter=shorter;
            this.longer=longer;
        }
        public String shorter() {
            return shorter;
        }
        public String longer() {
            return longer;
        }
        public String option(CommandLine cmdLine){
            return cmdLine.getOptionValue(longer);
        }
        public String option(CommandLine cmdLine,String defaultValue){
            return cmdLine.getOptionValue(longer,defaultValue);
        }
        public boolean selected(CommandLine cmdLine){
            return cmdLine.hasOption(longer);
        }
    }
    
    
    /** Standard mavenized projects hold sources in this folder */
    public static final String typicalSourceFolderRoot = "src/main/java";
    
    /** A typical pattern string for mutator methods prefixed with set/add/put. However the suffix part must represent a property name */
    public static final String typicalSetMethodPattern = "(set|add|put)[a-zA-Z0-9_]+";
    
    /** An OptionGroup for listing class names or file listing */
    private static final OptionGroup group= new OptionGroup()
                        .addOption(new Option(CLASS_NAMES.shorter(), CLASS_NAMES.longer(), true, "A comma separated set of canonical POJO class names"))
                        .addOption(new Option(FILE_LISTING.shorter(), FILE_LISTING.longer(), true,
                                "A text file in the class path that contains each class name (canonical name) in a line"));
    /** A set of command line options. */
    private static final Options cmdLineOptions = new Options()
            .addOption( SET_METHOD_PATTERN.shorter(), 
                        SET_METHOD_PATTERN.longer(), true, "A Regular Expression representing the typical setters of a pojo")
            .addOption( SRC_FOLDER.shorter(), 
                        SRC_FOLDER.longer(), true, "A Filesystem folder path where the source code will be generated")
            .addOptionGroup(group);
    
    /** A {@link SgClassPool} for writing source content of a class */
    private final SgClassPool sgPool = new SgClassPool();
    /** A {@link ClassPool} that can create a Builder interface given methods */
    private final ClassPool ctPool = new ClassPool(true);
    /** A {@link CtClass} for {@link Builder} from fluentinterface */
    private final CtClass fluentBuilderClass;
  
    // block
    {
        group.setRequired(true);
        try {
            fluentBuilderClass = ctPool.get(Builder.class.getCanonicalName());
        } catch (final NotFoundException e) {
            throw new IllegalStateException("Unable to get Builder class..perhaps not in path", e);
        }
    }
    /** A regex for method name pattern to include in builder. The suffix part of the method and argument must represent the name and type of property */
    private final Pattern setMethodNamePattern;
    /** A file system path where java source files for builder are generated */
    private final File sourceFolderRoot;

    /** A private constructor
     * 
     * @param methodNamePattern to hold the method name pattern for all of which a builder interface needs to be created
     * @param sourceFolder is the file system path to which the files are generated */
    private FluentBuilders(final Pattern methodNamePattern, final File sourceFolder) {
        setMethodNamePattern = methodNamePattern;
        sourceFolderRoot = sourceFolder;
    }

    /** A method to deal with any filtering/transforming the content of SgClass passed. <br>
     * For eg: {@link SgClass} for an interface when directly written adds interface keyword twice which needs to be eliminated
     * 
     * @param sgClass an instance of {@link SgClass}
     * @return processed source content passed {@link SgClass} */
    private String processSgClassContent(final Class<?> clz, final SgClass sgClass) {
        assert sgClass != null;
        return sgClass
                .toString()
                .replace("public abstract interface interface", "public abstract interface")
                .replace("extends com.fluentinterface.builder.Builder", "extends com.fluentinterface.builder.Builder<" + clz.getCanonicalName() + ">");
    }

    /** Writes content to a File
     * 
     * @param file to write content
     * @param content to write to a file */
    private void writeToFile(final File file, final String content) {
        assert file != null;
        file.delete();
        try {
            file.getParentFile().mkdirs();
            file.createNewFile();
        } catch (IOException e1) {
            throw new IllegalStateException(e1);
        }
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) {
            bw.write(content);
        } catch (final Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /** Adds a method signature to a builder interface
     * 
     * @param interfaceCtClass the {@link CtClass} representing the Builder interface
     * @param methods the set of {@link CtMethod}s representing the builder methods
     * @return true if the method is a Builder method (such as setXYZ(...))
     * @throws CannotCompileException
     * @throws NotFoundException */
    private void addMethodToBuilder(final CtClass interfaceCtClass, final List<CtMethod> methods) throws CannotCompileException, NotFoundException {
        for(CtMethod method:methods)
            interfaceCtClass.addMethod(new CtMethod(interfaceCtClass, 
                    method.getName(), method.getParameterTypes(), interfaceCtClass));
    }

    /** Return true if class passed is <b>not</b> a public class
     * 
     * @param thisPojoClass to be verified if its public
     * @return true if thisPojoClass is <b>not</b> public */
    private boolean isNotAPublicClass(final Class<?> thisPojoClass) {
        return thisPojoClass != null && (Modifier.PUBLIC & thisPojoClass.getModifiers()) != Modifier.PUBLIC;
    }
    
    /**
     * Gets a list of writable methods / mutator methods.
     * <br>
     * TODO: Must ensure that this class has mutators for the properties and not just some setters for random purpose<commit_msg>File generated will be overwritten in any case so file is opened in overwrite mode instead of append<commit_after>package com.github.venkateshamurthy.designpatterns.builders;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.CLASS_NAMES;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.FILE_LISTING;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.HELP;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.SET_METHOD_PATTERN;
import static com.github.venkateshamurthy.designpatterns.builders.FluentBuilders.CMD_OPTIONS.SRC_FOLDER;

import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.fuin.srcgen4javassist.SgClass;
import org.fuin.srcgen4javassist.SgClassPool;

import com.fluentinterface.ReflectionBuilder;
import com.fluentinterface.builder.Builder;

/** A Simple utility class to create Fluent Builders for a reasonably complex objects(POJOs) that are usually auto-generated with void returning set
 * methods. These Builders could come in handy when the number of properties in a POJO quickly run out of hand (say >4).
 * <p>
 * It basically creates an interface by capturing and adding the typical setter methods of an object (typically POJO) with an intent to proxy-wire the
 * builder interface to the pojo class
 * <p>
 * This class makes use of the following open source libraries to achieve the objective for which the author acknowledges and thanks by mentioning it
 * here.
 * <ol>
 * <li><a href="http://www.javassist.org"><b><i>javassist</i></b> - to create interface object dynamically</a></li>
 * <li><a href="http://www.fuin.org/srcgen4javassist/index.html"><i><b> srcgen4javassist</b></i> - to write the interface source in human readable
 * source code format</a></li>
 * <li><a href="https://github.com/davidmarquis/fluent-interface-proxy"><b><i> fluentinterface</i></b> - for actually generating the proxy builder for
 * the pojo using the interface source file</a></li>
 * </ol>
 * <p>
 * <b>How to use for creating the builder(usually during the maven source generation phase):</b> <br>
 * <br>
 * <code>
 * FluentBuilders builders={@link #create() FluentBuilders.create()};<br>
 * Class[] classes= ...(List of pojo class objects);<br>
 * {@link #writeInterface(Class...) builders.writeInterface(classes)};
 * </code>
 * <p>
 * <b>How to use the auto-generated builder for pojo:</b><br>
 * <br>
 * Assume the following: <ll>
 * <li>A POJO class called ErrorInfo exists that has few properties including <code>internalErrorCode</code> and <code>serviceErrorCode</code>.
 * <li>An ErrorInfoBuilder for building ErrorInfo is auto-generated during source generation. </ll> <br>
 * <br>
 * Now,in order to create Builder and use:<br>
 * <br>
 * <code>
 * final ErrorInfoBuilder errBuilder = FluentBuilders.builder(ErrorInfo.class);<br>
 * final ErrorInfo errInfo =
 * errBuilder.setInternalErrorCode(1234L).setServiceErrorCode(2134L).build();
 * </code>
 * 
 * @author <EMAIL> */
public class FluentBuilders {
    /** Command options enum*/
    protected static enum CMD_OPTIONS{
        FILE_LISTING("file","file-listing"),
        CLASS_NAMES("cls","classes"),
        SRC_FOLDER("src","src-folder-name"),
        SET_METHOD_PATTERN("set","set-method-pattern"),
        HELP("h","help");
        private final String shorter;
        private final String longer;
        CMD_OPTIONS(String shorter,String longer){
            this.shorter=shorter;
            this.longer=longer;
        }
        public String shorter() {
            return shorter;
        }
        public String longer() {
            return longer;
        }
        public String option(CommandLine cmdLine){
            return cmdLine.getOptionValue(longer);
        }
        public String option(CommandLine cmdLine,String defaultValue){
            return cmdLine.getOptionValue(longer,defaultValue);
        }
        public boolean selected(CommandLine cmdLine){
            return cmdLine.hasOption(longer);
        }
    }
    
    
    /** Standard mavenized projects hold sources in this folder */
    public static final String typicalSourceFolderRoot = "src/main/java";
    
    /** A typical pattern string for mutator methods prefixed with set/add/put. However the suffix part must represent a property name */
    public static final String typicalSetMethodPattern = "(set|add|put)[a-zA-Z0-9_]+";
    
    /** An OptionGroup for listing class names or file listing */
    private static final OptionGroup group= new OptionGroup()
                        .addOption(new Option(CLASS_NAMES.shorter(), CLASS_NAMES.longer(), true, "A comma separated set of canonical POJO class names"))
                        .addOption(new Option(FILE_LISTING.shorter(), FILE_LISTING.longer(), true,
                                "A text file in the class path that contains each class name (canonical name) in a line"));
    /** A set of command line options. */
    private static final Options cmdLineOptions = new Options()
            .addOption( SET_METHOD_PATTERN.shorter(), 
                        SET_METHOD_PATTERN.longer(), true, "A Regular Expression representing the typical setters of a pojo")
            .addOption( SRC_FOLDER.shorter(), 
                        SRC_FOLDER.longer(), true, "A Filesystem folder path where the source code will be generated")
            .addOptionGroup(group);
    
    /** A {@link SgClassPool} for writing source content of a class */
    private final SgClassPool sgPool = new SgClassPool();
    /** A {@link ClassPool} that can create a Builder interface given methods */
    private final ClassPool ctPool = new ClassPool(true);
    /** A {@link CtClass} for {@link Builder} from fluentinterface */
    private final CtClass fluentBuilderClass;
  
    // block
    {
        group.setRequired(true);
        try {
            fluentBuilderClass = ctPool.get(Builder.class.getCanonicalName());
        } catch (final NotFoundException e) {
            throw new IllegalStateException("Unable to get Builder class..perhaps not in path", e);
        }
    }
    /** A regex for method name pattern to include in builder. The suffix part of the method and argument must represent the name and type of property */
    private final Pattern setMethodNamePattern;
    /** A file system path where java source files for builder are generated */
    private final File sourceFolderRoot;

    /** A private constructor
     * 
     * @param methodNamePattern to hold the method name pattern for all of which a builder interface needs to be created
     * @param sourceFolder is the file system path to which the files are generated */
    private FluentBuilders(final Pattern methodNamePattern, final File sourceFolder) {
        setMethodNamePattern = methodNamePattern;
        sourceFolderRoot = sourceFolder;
    }

    /** A method to deal with any filtering/transforming the content of SgClass passed. <br>
     * For eg: {@link SgClass} for an interface when directly written adds interface keyword twice which needs to be eliminated
     * 
     * @param sgClass an instance of {@link SgClass}
     * @return processed source content passed {@link SgClass} */
    private String processSgClassContent(final Class<?> clz, final SgClass sgClass) {
        assert sgClass != null;
        return sgClass
                .toString()
                .replace("public abstract interface interface", "public abstract interface")
                .replace("extends com.fluentinterface.builder.Builder", "extends com.fluentinterface.builder.Builder<" + clz.getCanonicalName() + ">");
    }

    /** Writes content to a File
     * 
     * @param file to write content
     * @param content to write to a file */
    private void writeToFile(final File file, final String content) {
        assert file != null;
        file.delete();
        try {
            file.getParentFile().mkdirs();
            file.createNewFile();
        } catch (IOException e1) {
            throw new IllegalStateException(e1);
        }
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, false))) {
            bw.write(content);
        } catch (final Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /** Adds a method signature to a builder interface
     * 
     * @param interfaceCtClass the {@link CtClass} representing the Builder interface
     * @param methods the set of {@link CtMethod}s representing the builder methods
     * @return true if the method is a Builder method (such as setXYZ(...))
     * @throws CannotCompileException
     * @throws NotFoundException */
    private void addMethodToBuilder(final CtClass interfaceCtClass, final List<CtMethod> methods) throws CannotCompileException, NotFoundException {
        for(CtMethod method:methods)
            interfaceCtClass.addMethod(new CtMethod(interfaceCtClass, 
                    method.getName(), method.getParameterTypes(), interfaceCtClass));
    }

    /** Return true if class passed is <b>not</b> a public class
     * 
     * @param thisPojoClass to be verified if its public
     * @return true if thisPojoClass is <b>not</b> public */
    private boolean isNotAPublicClass(final Class<?> thisPojoClass) {
        return thisPojoClass != null && (Modifier.PUBLIC & thisPojoClass.getModifiers()) != Modifier.PUBLIC;
    }
    
    /**
     * Gets a list of writable methods / mutator methods.
     * <br>
     * TODO: Must ensure that this class has mutators for the properties and not just some setters for random purpose
{'fraction_non_alphanumeric': 0.09552495697074011, 'fraction_numerical': 0.0016255498183209026, 'mean_word_length': 3.3324357912178955, 'pattern_counts': {'":': 0, '<': 139, '<?xml version=': 0, '>': 141, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5568174', 'n_tokens_mistral': 5790, 'n_tokens_neox': 5622, 'n_words': 1826}