| /* |
| * 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 |
| * |
| * 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.apache.myfaces.buildtools.maven2.plugin.builder; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.apache.maven.plugin.AbstractMojo; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.model.ComponentMeta; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.model.Model; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.utils.BuildException; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.utils.MavenPluginConsoleLogSystem; |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.utils.MyfacesUtils; |
| import org.apache.velocity.Template; |
| import org.apache.velocity.VelocityContext; |
| import org.apache.velocity.app.VelocityEngine; |
| import org.apache.velocity.context.Context; |
| import org.apache.velocity.runtime.RuntimeConstants; |
| import org.codehaus.plexus.util.IOUtil; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| import com.thoughtworks.qdox.JavaDocBuilder; |
| import com.thoughtworks.qdox.model.AbstractJavaEntity; |
| import com.thoughtworks.qdox.model.Annotation; |
| import com.thoughtworks.qdox.model.DocletTag; |
| import com.thoughtworks.qdox.model.JavaClass; |
| import com.thoughtworks.qdox.model.JavaField; |
| import com.thoughtworks.qdox.model.JavaMethod; |
| |
| /** |
| * Maven goal to generate java source code for Component classes. |
| * |
| * <p>It uses velocity to generate templates, and has the option to define custom templates.</p> |
| * <p>The executed template has the following variables available to it:</p> |
| * <ul> |
| * <li>utils : Returns an instance of |
| * org.apache.myfaces.buildtools.maven2.plugin.builder.utils.MyfacesUtils, |
| * it contains some useful methods.</li> |
| * <li>component : Returns the current instance of |
| * org.apache.myfaces.buildtools.maven2.plugin.builder.model.ComponentMeta</li> |
| * <li>innersource : code to be injected from the template class when template |
| * mode is used</li> |
| * </ul> |
| * |
| * @version $Id$ |
| * @requiresDependencyResolution compile |
| * @goal make-components |
| * @phase generate-sources |
| */ |
| public class MakeComponentsMojo extends AbstractMojo |
| { |
| /** |
| * Injected Maven project. |
| * |
| * @parameter expression="${project}" |
| * @readonly |
| */ |
| private MavenProject project; |
| |
| /** |
| * Defines the directory where the metadata file (META-INF/myfaces-metadata.xml) is loaded. |
| * |
| * @parameter expression="${project.build.directory}/generated-resources/myfaces-builder-plugin" |
| */ |
| private File buildDirectory; |
| |
| /** |
| * Injected name of file generated by earlier run of BuildMetaDataMojo goal. |
| * |
| * @parameter |
| */ |
| private String metadataFile = "META-INF/myfaces-metadata.xml"; |
| |
| /** |
| * The directory used to load templates into velocity environment. |
| * |
| * @parameter expression="src/main/resources/META-INF" |
| */ |
| private File templateSourceDirectory; |
| |
| /** |
| * The directory where all generated files are created. This directory is added as a |
| * compile source root automatically like src/main/java is. |
| * |
| * @parameter expression="${project.build.directory}/generated-sources/myfaces-builder-plugin" |
| */ |
| private File generatedSourceDirectory; |
| |
| /** |
| * Generate only component classes that starts with the specified package prefix. |
| * |
| * @parameter |
| */ |
| private String packageContains; |
| |
| /** |
| * Generate only component classes that starts with the specified component type prefix. |
| * |
| * @parameter |
| */ |
| private String typePrefix; |
| |
| /** |
| * Log and continue execution when generating component classes. |
| * <p> |
| * If this property is set to false (default), errors when a component class is generated stops |
| * execution immediately. |
| * </p> |
| * |
| * @parameter |
| */ |
| private boolean force; |
| |
| /** |
| * Defines the jsf version (1.1 or 1.2), used to take the default templates for each version. |
| * <p> |
| * If version is 1.1, the default templateComponentName is 'componentClass11.vm', if version |
| * is 1.2 the default templateComponentName is 'componentClass12.vm', if version is 2.0 the |
| * default templateComponentName is 'componentClass20.vm' |
| * </p> |
| * |
| * @parameter |
| */ |
| private String jsfVersion; |
| |
| /** |
| * Define the models that should be included when generate component classes. If not set, the |
| * current model identified by the artifactId is used. |
| * <p> |
| * Each model built by build-metadata goal has a modelId, that by default is the artifactId of |
| * the project. Setting this property defines which objects tied in a specified modelId should |
| * be taken into account. |
| * </p> |
| * <p>In this case, limit component generation only to the components defined in the models |
| * identified by the modelId defined. </p> |
| * <p>This is useful when you need to generate files that take information defined on other |
| * projects</p> |
| * <p>Example:</p> |
| * <pre> |
| * <modelIds> |
| * <modelId>model1</modelId> |
| * <modelId>model2</modelId> |
| * </modelIds> |
| * </pre> |
| * |
| * @parameter |
| */ |
| private List modelIds; |
| |
| /** |
| * The name of the template used to generate component classes. According to the value on |
| * jsfVersion property the default if this property is not set could be componentClass11.vm (1.1) or |
| * componentClass12.vm (1.2) |
| * |
| * @parameter |
| */ |
| private String templateComponentName; |
| |
| /** |
| * This param is used to search in this folder if some file to |
| * be generated exists and avoid generation and duplicate exception. |
| * |
| * @parameter expression="src/main/java" |
| */ |
| private File mainSourceDirectory; |
| |
| /** |
| * This param is used to search in this folder if some file to |
| * be generated exists and avoid generation and duplicate exception. |
| * |
| * @parameter |
| */ |
| private File mainSourceDirectory2; |
| |
| /** |
| * Execute the Mojo. |
| */ |
| public void execute() throws MojoExecutionException |
| { |
| // This command makes Maven compile the generated source: |
| // getProject().addCompileSourceRoot( absoluteGeneratedPath.getPath() ); |
| |
| try |
| { |
| project.addCompileSourceRoot( generatedSourceDirectory.getCanonicalPath() ); |
| |
| if (modelIds == null) |
| { |
| modelIds = new ArrayList(); |
| modelIds.add(project.getArtifactId()); |
| } |
| Model model = IOUtils.loadModel(new File(buildDirectory, |
| metadataFile)); |
| new Flattener(model).flatten(); |
| generateComponents(model); |
| } |
| catch (IOException e) |
| { |
| throw new MojoExecutionException("Error generating components", e); |
| } |
| catch (BuildException e) |
| { |
| throw new MojoExecutionException("Error generating components", e); |
| } |
| } |
| |
| |
| private VelocityEngine initVelocity() throws MojoExecutionException |
| { |
| File template = new File(templateSourceDirectory, _getTemplateName()); |
| |
| if (template.exists()) |
| { |
| getLog().info("Using template from file loader: "+template.getPath()); |
| } |
| else |
| { |
| getLog().info("Using template from class loader: META-INF/"+_getTemplateName()); |
| } |
| |
| VelocityEngine velocityEngine = null; |
| try |
| { |
| velocityEngine = new VelocityEngine(); |
| velocityEngine.setProperty( "resource.loader", "file, class" ); |
| velocityEngine.setProperty( "file.resource.loader.class", |
| "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); |
| velocityEngine.setProperty( "file.resource.loader.path", templateSourceDirectory.getPath()); |
| velocityEngine.setProperty( "class.resource.loader.class", |
| "org.apache.myfaces.buildtools.maven2.plugin.builder.utils.RelativeClasspathResourceLoader" ); |
| velocityEngine.setProperty( "class.resource.loader.path", "META-INF"); |
| velocityEngine.setProperty( "velocimacro.library", "componentClassMacros11.vm"); |
| velocityEngine.setProperty( "velocimacro.permissions.allow.inline","true"); |
| velocityEngine.setProperty( "velocimacro.permissions.allow.inline.local.scope", "true"); |
| velocityEngine.setProperty( "directive.foreach.counter.initial.value","0"); |
| //velocityEngine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, |
| // "org.apache.myfaces.buildtools.maven2.plugin.builder.utils.ConsoleLogSystem" ); |
| |
| velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, |
| new MavenPluginConsoleLogSystem(this.getLog())); |
| velocityEngine.init(); |
| } |
| catch (Exception e) |
| { |
| throw new MojoExecutionException("Error creating VelocityEngine", e); |
| } |
| |
| return velocityEngine; |
| } |
| |
| |
| /** |
| * Generates parsed components. |
| */ |
| private void generateComponents(Model model) throws IOException, |
| MojoExecutionException |
| { |
| // Make sure generated source directory |
| // is added to compilation source path |
| //project.addCompileSourceRoot(generatedSourceDirectory.getCanonicalPath()); |
| |
| //Init Qdox for extract code |
| JavaDocBuilder builder = new JavaDocBuilder(); |
| |
| List sourceDirs = project.getCompileSourceRoots(); |
| |
| // need a File object representing the original source tree |
| for (Iterator i = sourceDirs.iterator(); i.hasNext();) |
| { |
| String srcDir = (String) i.next(); |
| builder.addSourceTree(new File(srcDir)); |
| } |
| |
| //Init velocity |
| VelocityEngine velocityEngine = initVelocity(); |
| |
| VelocityContext baseContext = new VelocityContext(); |
| baseContext.put("utils", new MyfacesUtils()); |
| |
| for (Iterator it = model.getComponents().iterator(); it.hasNext();) |
| { |
| ComponentMeta component = (ComponentMeta) it.next(); |
| |
| if (component.getClassName() != null) |
| { |
| File f = new File(mainSourceDirectory, StringUtils.replace( |
| component.getClassName(), ".", "/")+".java"); |
| |
| if (!f.exists() && canGenerateComponent(component)) |
| { |
| if (mainSourceDirectory2 != null) |
| { |
| File f2 = new File(mainSourceDirectory2, StringUtils.replace( |
| component.getClassName(), ".", "/")+".java"); |
| if (f2.exists()) |
| { |
| //Skip |
| continue; |
| } |
| } |
| getLog().info("Generating component class:"+component.getClassName()); |
| |
| try |
| { |
| _generateComponent(velocityEngine, builder,component,baseContext); |
| } |
| catch(MojoExecutionException e) |
| { |
| if (force) |
| { |
| getLog().error(e.getMessage()); |
| } |
| else |
| { |
| //Stop execution throwing exception |
| throw e; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public boolean canGenerateComponent(ComponentMeta component) |
| { |
| if ( modelIds.contains(component.getModelId()) |
| && includePackage(component) |
| && includeType(component)) |
| { |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| public boolean includePackage(ComponentMeta component) |
| { |
| if (packageContains != null) |
| { |
| if (MyfacesUtils.getPackageFromFullClass(component.getClassName()).startsWith(packageContains)) |
| { |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| else |
| { |
| return true; |
| } |
| } |
| |
| public boolean includeType(ComponentMeta component) |
| { |
| if (typePrefix != null) |
| { |
| if (component.getType().startsWith(typePrefix)) |
| { |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| else |
| { |
| return true; |
| } |
| } |
| |
| |
| /** |
| * Generates a parsed component. |
| * |
| * @param component |
| * the parsed component metadata |
| */ |
| private void _generateComponent(VelocityEngine velocityEngine, |
| JavaDocBuilder builder, |
| ComponentMeta component, VelocityContext baseContext) |
| throws MojoExecutionException |
| { |
| Context context = new VelocityContext(baseContext); |
| context.put("component", component); |
| |
| if (Boolean.TRUE.equals(component.isTemplate())) |
| { |
| String source = this.getInnerSourceCode(builder, component); |
| |
| if (source != null && !"".equals(source)) |
| { |
| context.put("innersource", source); |
| } |
| } |
| |
| Writer writer = null; |
| File outFile = null; |
| |
| try |
| { |
| outFile = new File(generatedSourceDirectory, StringUtils.replace( |
| component.getClassName(), ".", "/")+".java"); |
| |
| if ( !outFile.getParentFile().exists() ) |
| { |
| outFile.getParentFile().mkdirs(); |
| } |
| |
| writer = new OutputStreamWriter(new FileOutputStream(outFile)); |
| |
| Template template = velocityEngine.getTemplate(_getTemplateName()); |
| |
| template.merge(context, writer); |
| |
| writer.flush(); |
| } |
| catch (Exception e) |
| { |
| throw new MojoExecutionException( |
| "Error merging velocity templates: " + e.getMessage(), e); |
| } |
| finally |
| { |
| IOUtil.close(writer); |
| writer = null; |
| } |
| } |
| |
| /** |
| * This method extract from component scanned sourceClass, the parts |
| * which we need to move to generated class as a single String using Qdox. |
| * |
| * Ignore code that has a \@JSFExclude or \@JSFProperty Doclet or Annotation |
| * |
| * @param component |
| * @return |
| */ |
| private String getInnerSourceCode(JavaDocBuilder builder, ComponentMeta component) |
| { |
| StringWriter writer = new StringWriter(); |
| |
| JavaClass sourceClass = builder.getClassByName(component.getSourceClassName()); |
| |
| JavaField [] fields = sourceClass.getFields(); |
| |
| //Include the fields defined |
| for (int i = 0; i < fields.length; i++) |
| { |
| JavaField field = fields[i]; |
| |
| DocletTag tag = field.getTagByName("JSFExclude"); |
| Annotation anno = getAnnotation(field, "JSFExclude"); |
| |
| if (!(tag == null && anno == null)) |
| { |
| continue; |
| } |
| |
| if (!isExcludedField(field.getName())) |
| { |
| writer.write(" "); |
| writer.write(field.getDeclarationSignature(true)); |
| String initExpr = field.getInitializationExpression(); |
| initExpr = cleanInitializationExpression(initExpr); |
| if (initExpr != null) |
| { |
| writer.write(" = "); |
| writer.write(initExpr); |
| } |
| writer.write(';'); |
| writer.write('\n'); |
| } |
| } |
| |
| JavaMethod [] methods = sourceClass.getMethods(); |
| for (int i = 0; i < methods.length; i++) |
| { |
| JavaMethod method = methods[i]; |
| |
| DocletTag tag = method.getTagByName("JSFExclude"); |
| Annotation anno = getAnnotation(method, "JSFExclude"); |
| |
| if (!(tag == null && anno == null)) |
| { |
| continue; |
| } |
| |
| tag = method.getTagByName("JSFProperty", false); |
| anno = getAnnotation(method, "JSFProperty"); |
| |
| if ( (tag == null && anno == null) || !method.isAbstract()) |
| { |
| //Get declaration signature in a way that we don't need |
| //to declare imports. |
| String declaration = method.getDeclarationSignature(true); |
| |
| writer.write(" "); |
| writer.write(declaration); |
| String sourceCode = method.getSourceCode(); |
| if(sourceCode != null && sourceCode.length() > 0) |
| { |
| writer.write('\n'); |
| writer.write(" "); |
| writer.write('{'); |
| writer.write(method.getSourceCode()); |
| writer.write('}'); |
| writer.write('\n'); |
| writer.write('\n'); |
| } |
| else |
| { |
| writer.write(';'); |
| writer.write('\n'); |
| } |
| } |
| |
| } |
| |
| return writer.toString(); |
| } |
| |
| private boolean isExcludedField(String name) |
| { |
| return (name.equals("COMPONENT_TYPE") || |
| name.equals("DEFAULT_RENDERER_TYPE") || |
| name.equals("COMPONENT_FAMILY") ); |
| } |
| |
| /** |
| * Clean up a field initialisation expression returned by qdox. |
| * <p> |
| * When a class has "int foo = 1;" then the initialisation expression |
| * is the bit after the equals sign. Unfortunately qdox tends to return |
| * a lot of garbage stuff in there. In particular, if there is no |
| * initialisation expression, then qdox can return a string with |
| * whitespace (not a null or empty string). |
| * <p> |
| * In addition, if there are comments *before* a field declaration, |
| * they appear inside the initialisation text (this is just broken |
| * behaviour in qdox). Ideally we would move them to above the field |
| * declaration where they belong. However that is complicated and |
| * fragile, so we just discard them here. |
| */ |
| static String cleanInitializationExpression(String expr) |
| { |
| expr = StringUtils.trim(expr); |
| if (StringUtils.isEmpty(expr)) |
| { |
| return null; |
| } |
| |
| // split on linefeeds |
| // trim each separately |
| // remove any comment chars |
| |
| if (expr.indexOf("\n") > -1) |
| { |
| StringBuffer buf = new StringBuffer(100); |
| String[] lines = StringUtils.split(expr, "\n"); |
| for(int i=0; i<lines.length; ++i) |
| { |
| String line = lines[i]; |
| line = StringUtils.trim(line); |
| if (!line.startsWith("//") && !StringUtils.isEmpty(line)) |
| { |
| if (buf.length()> 0) |
| { |
| buf.append(" "); |
| } |
| buf.append(line); |
| } |
| } |
| |
| expr = buf.toString(); |
| } |
| |
| if (expr.startsWith("//")) |
| { |
| return null; |
| } |
| |
| if (StringUtils.isEmpty(expr)) |
| { |
| return null; |
| } |
| |
| return expr; |
| } |
| |
| /** |
| * TODO: Copied from QdoxModelBuilder! |
| * |
| * @param entity |
| * @param annoName |
| * @return |
| */ |
| private Annotation getAnnotation(AbstractJavaEntity entity, String annoName) |
| { |
| Annotation[] annos = entity.getAnnotations(); |
| if (annos == null) |
| { |
| return null; |
| } |
| // String wanted = ANNOTATION_BASE + "." + annoName; |
| for (int i = 0; i < annos.length; ++i) |
| { |
| Annotation thisAnno = annos[i]; |
| // Ideally, here we would check whether the fully-qualified name of |
| // the annotation |
| // class matches ANNOTATION_BASE + "." + annoName. However it |
| // appears that qdox 1.6.3 |
| // does not correctly expand @Foo using the class import statements; |
| // method |
| // Annotation.getType.getJavaClass.getFullyQualifiedName still just |
| // returns the short |
| // class name. So for now, just check for the short name. |
| String thisAnnoName = thisAnno.getType().getJavaClass().getName(); |
| |
| //Make short name for recognizing, if returns long |
| int containsPoint = thisAnnoName.lastIndexOf('.'); |
| if (containsPoint != -1) |
| { |
| thisAnnoName = thisAnnoName.substring(containsPoint+1); |
| } |
| if (thisAnnoName.equals(annoName)) |
| { |
| return thisAnno; |
| } |
| } |
| return null; |
| } |
| |
| private String _getTemplateName() |
| { |
| if (templateComponentName == null) |
| { |
| if (_is20()) |
| { |
| return "componentClass20.vm"; |
| } |
| else if (_is12()) |
| { |
| return "componentClass12.vm"; |
| } |
| else |
| { |
| return "componentClass11.vm"; |
| } |
| } |
| else |
| { |
| return templateComponentName; |
| } |
| } |
| |
| private boolean _is12() |
| { |
| return "1.2".equals(jsfVersion) || "12".equals(jsfVersion); |
| } |
| |
| private boolean _is20() |
| { |
| return "2.0".equals(jsfVersion) || "20".equals(jsfVersion); |
| } |
| } |