/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon;
import com.martiansoftware.jsap.FlaggedOption;
import com.martiansoftware.jsap.JSAP;
import com.martiansoftware.jsap.JSAPException;
import com.martiansoftware.jsap.JSAPResult;
import com.martiansoftware.jsap.Switch;
import com.martiansoftware.jsap.stringparsers.FileStringParser;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import spoon.SpoonModelBuilder.InputType;
import spoon.compiler.Environment;
import spoon.compiler.SpoonResource;
import spoon.compiler.SpoonResourceHelper;
import spoon.processing.Processor;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.factory.FactoryImpl;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.PrettyPrinter;
import spoon.reflect.visitor.filter.AbstractFilter;
import spoon.support.DefaultCoreFactory;
import spoon.support.JavaOutputProcessor;
import spoon.support.StandardEnvironment;
import spoon.support.compiler.FileSystemFile;
import spoon.support.compiler.FileSystemFolder;
import spoon.support.compiler.jdt.JDTBasedSpoonCompiler;
import spoon.support.gui.SpoonModelTree;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.ResourceBundle;
/**
* This class implements an integrated command-line launcher for processing
* programs at compile-time using the JDT-based builder (Eclipse). It takes
* arguments that allow building, processing, printing, and compiling Java
* programs. Launch with no arguments (see {@link #main(String[])}) for detailed
* usage.
*/
public class Launcher implements SpoonAPI {
public static final String SPOONED_CLASSES = "spooned-classes";
public static final String OUTPUTDIR = "spooned";
private final Factory factory;
private SpoonModelBuilder modelBuilder;
private String[] commandLineArgs = new String[0];
private Filter<CtType<?>> typeFilter;
/**
* Contains the arguments accepted by this launcher (available after
* construction and accessible by sub-classes).
*/
private static JSAP jsapSpec;
protected JSAPResult jsapActualArgs;
private List<String> processorTypes = new ArrayList<>();
private List<Processor<? extends CtElement>> processors = new ArrayList<>();
/**
* A default program entry point (instantiates a launcher with the given
* arguments and calls {@link #run()}).
*/
public static void main(String[] args) throws Exception {
new Launcher().run(args);
}
@Override
public void run(String[] args) {
this.setArgs(args);
if (args.length != 0) {
this.run();
// display GUI
if (this.jsapActualArgs.getBoolean("gui")) {
new SpoonModelTree(getFactory());
}
} else {
this.printUsage();
}
}
public void setArgs(String[] args2) {
this.commandLineArgs = args2;
processArguments();
}
public void printUsage() {
this.commandLineArgs = new String[] { "--help" };
processArguments();
}
static {
jsapSpec = defineArgs();
}
/**
* Creates a {@link Launcher} using the {@link Factory} returned by {@link #createFactory()}.
*/
public Launcher() {
factory = createFactory();
processArguments();
}
/**
* Creates a {@link Launcher} with {@link Factory} {@code pFactory}.
*
* @param pFactory
* The {@link Factory} that will be utilized in {@link #buildModel()}.
* @throws IllegalArgumentException
* If {@code pFactory == null}.
*/
public Launcher(final Factory pFactory) {
if (pFactory == null) {
throw new IllegalArgumentException("unable to create launcher with null factory");
}
factory = pFactory;
processArguments();
}
@Override
public void addInputResource(String path) {
File file = new File(path);
if (file.isDirectory()) {
addInputResource(new FileSystemFolder(file));
} else {
addInputResource(new FileSystemFile(file));
}
}
/** adds a resource to be parsed to build the spoon model */
public void addInputResource(SpoonResource resource) {
modelBuilder.addInputSource(resource);
}
@Override
public void addProcessor(String name) {
processorTypes.add(name);
}
@Override
public <T extends CtElement> void addProcessor(Processor<T> processor) {
processors.add(processor);
}
public void addTemplateResource(SpoonResource resource) {
modelBuilder.addTemplateSource(resource);
}
@Override
public Environment getEnvironment() {
return factory.getEnvironment();
}
/**
* Defines the common arguments for sub-launchers.
*
* @return the JSAP arguments
*/
protected static JSAP defineArgs() {
try {
// Verbose output
JSAP jsap = new JSAP();
// help
Switch sw1 = new Switch("help");
sw1.setShortFlag('h');
sw1.setLongFlag("help");
sw1.setDefault("false");
jsap.registerParameter(sw1);
// Tabs
sw1 = new Switch("tabs");
sw1.setLongFlag("tabs");
sw1.setDefault("false");
sw1.setHelp("Use tabulations instead of spaces in the generated code (use spaces by default).");
jsap.registerParameter(sw1);
// Tab size
FlaggedOption opt2 = new FlaggedOption("tabsize");
opt2.setLongFlag("tabsize");
opt2.setStringParser(JSAP.INTEGER_PARSER);
opt2.setDefault("4");
opt2.setHelp("Define tabulation size.");
jsap.registerParameter(opt2);
// Level logging.
opt2 = new FlaggedOption("level");
opt2.setLongFlag("level");
opt2.setHelp("Level of the ouput messages about what spoon is doing. Default value is ALL level.");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setDefault(Level.OFF.toString());
jsap.registerParameter(opt2);
// Auto-import
sw1 = new Switch("imports");
sw1.setLongFlag("with-imports");
sw1.setDefault("false");
sw1.setHelp("Enable imports in generated files.");
jsap.registerParameter(sw1);
// java compliance
opt2 = new FlaggedOption("compliance");
opt2.setLongFlag("compliance");
opt2.setHelp("Java source code compliance level (1,2,3,4,5, 6, 7 or 8).");
opt2.setStringParser(JSAP.INTEGER_PARSER);
opt2.setDefault("8");
jsap.registerParameter(opt2);
// compiler's encoding
opt2 = new FlaggedOption("encoding");
opt2.setLongFlag("encoding");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setRequired(false);
opt2.setDefault("UTF-8");
opt2.setHelp("Forces the compiler to use a specific encoding (UTF-8, UTF-16, ...).");
jsap.registerParameter(opt2);
// setting Input files & Directory
opt2 = new FlaggedOption("input");
opt2.setShortFlag('i');
opt2.setLongFlag("input");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setRequired(false);
opt2.setHelp("List of path to sources files.");
jsap.registerParameter(opt2);
// Processor qualified name
opt2 = new FlaggedOption("processors");
opt2.setShortFlag('p');
opt2.setLongFlag("processors");
opt2.setHelp("List of processor's qualified name to be used.");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setRequired(false);
jsap.registerParameter(opt2);
// setting input template
opt2 = new FlaggedOption("template");
opt2.setShortFlag('t');
opt2.setLongFlag("template");
opt2.setHelp("List of source templates.");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setRequired(false);
opt2.setHelp("List of path to templates java files.");
jsap.registerParameter(opt2);
// Spooned output directory
opt2 = new FlaggedOption("output");
opt2.setShortFlag('o');
opt2.setLongFlag("output");
opt2.setDefault(OUTPUTDIR);
opt2.setHelp("Specify where to place generated java files.");
opt2.setStringParser(FileStringParser.getParser());
opt2.setRequired(false);
jsap.registerParameter(opt2);
// Source classpath
opt2 = new FlaggedOption("source-classpath");
opt2.setLongFlag("source-classpath");
opt2.setHelp("An optional classpath to be passed to the internal " + "Java compiler when building or compiling the " + "input sources.");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setRequired(false);
jsap.registerParameter(opt2);
// Template classpath
opt2 = new FlaggedOption("template-classpath");
opt2.setLongFlag("template-classpath");
opt2.setHelp("An optional classpath to be passed to the " + "internal Java compiler when building " + "the template sources.");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setRequired(false);
jsap.registerParameter(opt2);
// Destination
opt2 = new FlaggedOption("destination");
opt2.setShortFlag('d');
opt2.setLongFlag("destination");
opt2.setDefault(SPOONED_CLASSES);
opt2.setHelp("An optional destination directory for the generated class files.");
opt2.setStringParser(FileStringParser.getParser());
opt2.setRequired(false);
jsap.registerParameter(opt2);
// Sets output type generation
opt2 = new FlaggedOption("output-type");
opt2.setLongFlag(opt2.getID());
String msg = "States how to print the processed source code: ";
int i = 0;
for (OutputType v : OutputType.values()) {
i++;
msg += v.toString();
if (i != OutputType.values().length) {
msg += "|";
}
}
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setHelp(msg);
opt2.setDefault("classes");
jsap.registerParameter(opt2);
// Enable compilation
sw1 = new Switch("compile");
sw1.setLongFlag(sw1.getUsageName());
sw1.setHelp("Compiles the resulting classes (after transformation) to bytecode.");
sw1.setDefault("false");
jsap.registerParameter(sw1);
// Enable pre-compilation
sw1 = new Switch("precompile");
sw1.setLongFlag("precompile");
sw1.setHelp("[experimental] Enable pre-compilation of input source files " + "before processing. The compiled classes " + "will be added to the classpath.");
sw1.setDefault("false");
jsap.registerParameter(sw1);
// Enable building only outdated files
sw1 = new Switch("buildOnlyOutdatedFiles");
sw1.setLongFlag("buildOnlyOutdatedFiles");
sw1.setHelp(
"Set Spoon to build only the source files that " + "have been modified since the latest " + "source code generation, for performance " + "purpose. Note that this option requires "
+ "to have the --ouput-type option not set " + "to none. This option is not appropriate " + "to all kinds of processing. In particular "
+ "processings that implement or rely on a " + "global analysis should avoid this option " + "because the processor will only have access "
+ "to the outdated source code (the files " + "modified since the latest processing).");
sw1.setDefault("false");
jsap.registerParameter(sw1);
sw1 = new Switch("lines");
sw1.setLongFlag("lines");
sw1.setHelp("Set Spoon to try to preserve the original line " + "numbers when generating the source " + "code (may lead to human-unfriendly " + "formatting).");
sw1.setDefault("false");
jsap.registerParameter(sw1);
// nobinding
sw1 = new Switch("noclasspath");
sw1.setShortFlag('x');
sw1.setLongFlag("noclasspath");
sw1.setHelp("Does not assume a full classpath");
jsap.registerParameter(sw1);
// show GUI
sw1 = new Switch("gui");
sw1.setShortFlag('g');
sw1.setLongFlag("gui");
sw1.setHelp("Show spoon model after processing");
jsap.registerParameter(sw1);
// Disable copy of resources.
sw1 = new Switch("no-copy-resources");
sw1.setShortFlag('r');
sw1.setLongFlag("no-copy-resources");
sw1.setHelp("Disable the copy of resources from source to destination folder.");
sw1.setDefault("false");
jsap.registerParameter(sw1);
// Enable generation of javadoc.
sw1 = new Switch("enable-comments");
sw1.setShortFlag('c');
sw1.setLongFlag("enable-comments");
sw1.setHelp("Adds all code comments in the Spoon AST (Javadoc, line-based comments), rewrites them when pretty-printing.");
sw1.setDefault("false");
jsap.registerParameter(sw1);
// Generate only java files specified.
opt2 = new FlaggedOption("generate-files");
opt2.setShortFlag('f');
opt2.setLongFlag("generate-files");
opt2.setHelp("Only generate the given fully qualified java classes (separated by ':' if multiple are given).");
opt2.setStringParser(JSAP.STRING_PARSER);
opt2.setRequired(false);
jsap.registerParameter(opt2);
// Disable checks.
sw1 = new Switch("disable-model-self-checks");
sw1.setShortFlag('a');
sw1.setLongFlag("disable-model-self-checks");
sw1.setHelp("Disables checks made on the AST (hashcode violation, method's signature violation and parent violation). Default: false.");
sw1.setDefault("false");
jsap.registerParameter(sw1);
return jsap;
} catch (JSAPException e) {
throw new SpoonException(e.getMessage(), e);
}
}
/**
* Returns the command-line given launching arguments in JSAP format.
*/
protected final JSAPResult getArguments() {
return parseArgs();
}
protected void processArguments() {
jsapActualArgs = getArguments();
Environment environment = factory.getEnvironment();
// environment initialization
environment.setComplianceLevel(jsapActualArgs.getInt("compliance"));
environment.setLevel(jsapActualArgs.getString("level"));
environment.setAutoImports(jsapActualArgs.getBoolean("imports"));
environment.setNoClasspath(jsapActualArgs.getBoolean("noclasspath"));
environment.setPreserveLineNumbers(jsapActualArgs.getBoolean("lines"));
environment.setTabulationSize(jsapActualArgs.getInt("tabsize"));
environment.useTabulations(jsapActualArgs.getBoolean("tabs"));
environment.setCopyResources(!jsapActualArgs.getBoolean("no-copy-resources"));
environment.setCommentEnabled(jsapActualArgs.getBoolean("enable-comments"));
environment.setShouldCompile(jsapActualArgs.getBoolean("compile"));
environment.setSelfChecks(jsapActualArgs.getBoolean("disable-model-self-checks"));
if (getArguments().getString("generate-files") != null) {
setOutputFilter(getArguments().getString("generate-files").split(":"));
}
// now we are ready to create a spoon compiler
modelBuilder = createCompiler();
if (getArguments().getString("input") != null) {
for (String s : getArguments().getString("input").split("[" + File.pathSeparatorChar + "]")) {
try {
modelBuilder.addInputSource(SpoonResourceHelper.createResource(new File(s)));
} catch (FileNotFoundException e) {
throw new SpoonException(e);
}
}
}
if (jsapActualArgs.getBoolean("precompile")) {
modelBuilder.compile(InputType.FILES);
getEnvironment().setSourceClasspath(new String[]{getEnvironment().getBinaryOutputDirectory()});
}
if (getArguments().getFile("output") != null) {
setSourceOutputDirectory(getArguments().getFile("output"));
}
// Adding template from command-line
if (getArguments().getString("template") != null) {
for (String s : getArguments().getString("template").split("[" + File.pathSeparatorChar + "]")) {
try {
modelBuilder.addTemplateSource(SpoonResourceHelper.createResource(new File(s)));
} catch (FileNotFoundException e) {
environment.report(null, Level.ERROR, "Unable to add template file: " + e.getMessage());
LOGGER.error(e.getMessage(), e);
}
}
}
if (getArguments().getString("processors") != null) {
for (String processorName : getArguments().getString("processors").split(File.pathSeparator)) {
addProcessor(processorName);
}
}
}
/**
* Gets the list of processor types to be initially applied during the
* processing (-p option).
*/
protected java.util.List<String> getProcessorTypes() {
return processorTypes;
}
/**
* Gets the list of processors instance to be initially applied during the
* processing.
*/
protected List<Processor<? extends CtElement>> getProcessors() {
return processors;
}
/**
* Parses the arguments given by the command line.
*
* @return the JSAP-presented arguments
*/
protected JSAPResult parseArgs() {
if (jsapSpec == null) {
throw new IllegalStateException("no args, please call setArgs before");
}
JSAPResult arguments = jsapSpec.parse(commandLineArgs);
if (!arguments.success()) {
// print out specific error messages describing the problems
for (java.util.Iterator<?> errs = arguments.getErrorMessageIterator(); errs.hasNext();) {
System.err.println("Error: " + errs.next());
}
}
if (!arguments.success() || arguments.getBoolean("help")) {
System.err.println(getVersionMessage());
System.err.println("Usage: java <launcher name> [option(s)]");
System.err.println();
System.err.println("Options : ");
System.err.println();
System.err.println(jsapSpec.getHelp());
System.exit(-1);
}
return arguments;
}
/**
* A default logger to be used by Spoon.
*/
public static final Logger LOGGER = Logger.getLogger(Launcher.class);
/**
* Creates a new Spoon Java compiler in order to process and compile Java
* source code.
*
* @param factory
* the factory this compiler works on
*/
public SpoonModelBuilder createCompiler(Factory factory) {
SpoonModelBuilder comp = new JDTBasedSpoonCompiler(factory);
Environment env = getEnvironment();
// building
comp.setEncoding(getArguments().getString("encoding"));
comp.setBuildOnlyOutdatedFiles(jsapActualArgs.getBoolean("buildOnlyOutdatedFiles"));
comp.setBinaryOutputDirectory(jsapActualArgs.getFile("destination"));
comp.setSourceOutputDirectory(jsapActualArgs.getFile("output"));
comp.setEncoding(jsapActualArgs.getString("encoding"));
// backward compatibility
// we don't have to set the source classpath
if (jsapActualArgs.contains("source-classpath")) {
comp.setSourceClasspath(jsapActualArgs.getString("source-classpath").split(System.getProperty("path.separator")));
}
env.debugMessage("output: " + comp.getSourceOutputDirectory());
env.debugMessage("destination: " + comp.getBinaryOutputDirectory());
env.debugMessage("source classpath: " + Arrays.toString(comp.getSourceClasspath()));
env.debugMessage("template classpath: " + Arrays.toString(comp.getTemplateClasspath()));
return comp;
}
public SpoonModelBuilder createCompiler(Factory factory, List<SpoonResource> inputSources) {
SpoonModelBuilder c = createCompiler(factory);
c.addInputSources(inputSources);
return c;
}
/**
* Creates a new Spoon Java compiler in order to process and compile Java
* source code.
*/
public SpoonModelBuilder createCompiler(Factory factory, List<SpoonResource> inputSources, List<SpoonResource> templateSources) {
SpoonModelBuilder c = createCompiler(factory);
c.addInputSources(inputSources);
c.addTemplateSources(templateSources);
return c;
}
@Override
public SpoonModelBuilder createCompiler() {
return createCompiler(factory);
}
/**
* Creates a new Spoon Java compiler with a default factory and a list of
* input sources.
*/
public SpoonModelBuilder createCompiler(List<SpoonResource> inputSources) {
SpoonModelBuilder c = createCompiler(factory);
c.addInputSources(inputSources);
return c;
}
@Override
public Factory createFactory() {
return new FactoryImpl(new DefaultCoreFactory(), createEnvironment());
}
@Override
public Factory getFactory() {
return factory;
}
@Override
public Environment createEnvironment() {
return new StandardEnvironment();
}
public JavaOutputProcessor createOutputWriter(File sourceOutputDir, Environment environment) {
return new JavaOutputProcessor(sourceOutputDir, createPrettyPrinter());
}
public PrettyPrinter createPrettyPrinter() {
return new DefaultJavaPrettyPrinter(getEnvironment());
}
/**
* Runs Spoon using the given compiler, with the given run options. A Spoon
* run will perform the following tasks:
*
* <ol>
* <li>Source model building in the given compiler:
* {@link SpoonModelBuilder#build()}.</li>
* <li>Template model building in the given factory (if any template source
* is given): {@link SpoonModelBuilder#build()}.</li>
* <li>Model processing with the list of given processors if any:
* {@link SpoonModelBuilder#instantiateAndProcess(List)}.</li>
* <li>Processed Source code printing and generation (can be disabled with
* {@link OutputType#NO_OUTPUT}):
* {@link SpoonModelBuilder#generateProcessedSourceFiles(OutputType)}.</li>
* <li>Processed source code compilation (optional):
* </ol>
*/
@Override
public void run() {
Environment env = modelBuilder.getFactory().getEnvironment();
env.reportProgressMessage(getVersionMessage());
env.reportProgressMessage("running Spoon...");
env.reportProgressMessage("start processing...");
long t = 0;
long tstart = System.currentTimeMillis();
buildModel();
process();
prettyprint();
if (env.shouldCompile()) {
// we compile the types from the factory, they may have been modified by some processors
modelBuilder.compile(InputType.CTTYPES);
}
t = System.currentTimeMillis();
env.debugMessage("program spooning done in " + (t - tstart) + " ms");
env.reportEnd();
}
private String getVersionMessage() {
return "Spoon version " + ResourceBundle.getBundle("spoon").getString("application.version");
}
public static final IOFileFilter RESOURCES_FILE_FILTER = new IOFileFilter() {
@Override
public boolean accept(File file) {
return !file.getName().endsWith(".java");
}
@Override
public boolean accept(File file, String s) {
return false;
}
};
public static final IOFileFilter ALL_DIR_FILTER = new IOFileFilter() {
@Override
public boolean accept(File file) {
return true;
}
@Override
public boolean accept(File file, String s) {
return false;
}
};
@Override
public void buildModel() {
long tstart = System.currentTimeMillis();
modelBuilder.build();
getEnvironment().debugMessage("model built in " + (System.currentTimeMillis() - tstart));
}
@Override
public void process() {
long tstart = System.currentTimeMillis();
modelBuilder.instantiateAndProcess(getProcessorTypes());
modelBuilder.process(getProcessors());
getEnvironment().debugMessage("model processed in " + (System.currentTimeMillis() - tstart) + " ms");
}
@Override
public void prettyprint() {
OutputType outputType = OutputType.fromString(jsapActualArgs.getString("output-type"));
long tstart = System.currentTimeMillis();
try {
modelBuilder.generateProcessedSourceFiles(outputType, typeFilter);
} catch (Exception e) {
throw new SpoonException(e);
}
if (!outputType.equals(OutputType.NO_OUTPUT) && getEnvironment().isCopyResources()) {
for (File dirInputSource : modelBuilder.getInputSources()) {
if (dirInputSource.isDirectory()) {
final Collection<?> resources = FileUtils.listFiles(dirInputSource, RESOURCES_FILE_FILTER, ALL_DIR_FILTER);
for (Object resource : resources) {
final String resourceParentPath = ((File) resource).getParent();
final String packageDir = resourceParentPath.substring(dirInputSource.getPath().length());
final String targetDirectory = modelBuilder.getSourceOutputDirectory() + packageDir;
try {
FileUtils.copyFileToDirectory((File) resource, new File(targetDirectory));
} catch (IOException e) {
throw new SpoonException(e);
}
}
}
}
}
getEnvironment().debugMessage("pretty-printed in " + (System.currentTimeMillis() - tstart) + " ms");
}
public SpoonModelBuilder getModelBuilder() {
return modelBuilder;
}
@Override
public void setSourceOutputDirectory(String path) {
setSourceOutputDirectory(new File(path));
}
@Override
public void setSourceOutputDirectory(File outputDirectory) {
modelBuilder.setSourceOutputDirectory(outputDirectory);
getEnvironment().setDefaultFileGenerator(createOutputWriter(outputDirectory, getEnvironment()));
}
@Override
public void setOutputFilter(Filter<CtType<?>> typeFilter) {
this.typeFilter = typeFilter;
}
@Override
public void setOutputFilter(final String... qualifedNames) {
setOutputFilter(new AbstractFilter<CtType<?>>(CtType.class) {
@Override
public boolean matches(CtType<?> element) {
for (String generateFile : qualifedNames) {
if (generateFile.equals(element.getQualifiedName())) {
return true;
}
}
return false;
}
});
}
@Override
public void setBinaryOutputDirectory(String path) {
getFactory().getEnvironment().setBinaryOutputDirectory(path);
}
@Override
public void setBinaryOutputDirectory(File outputDirectory) {
setBinaryOutputDirectory(outputDirectory.getPath());
}
@Override
public CtModel getModel() {
return factory.getModel();
}
}