package org.jboss.mockgenerator; /* * Copyright 2001-2005 The Apache Software Foundation. * * 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. */ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.jboss.mockgenerator.config.Mock; import org.jboss.mockgenerator.config.MockConfig; import org.jboss.mockgenerator.config.MockMethod; import org.jboss.mockgenerator.config.io.xpp3.MockConfigXpp3Reader; /** */ public abstract class AbstractMockMojo extends AbstractMojo { /** * Top maven project. * * @parameter expression="${project}" * @readonly */ protected MavenProject project; /** * Location of the compiled java classes. * * @parameter expression="${project.build.directory}" * @required */ protected File outputDirectory; /** * <p class="changed_added_4_0"> * Project classloader that able to load classes from project dependencies. * </p> */ protected ClassLoader projectClassLoader; /** * <p class="changed_added_4_0"> * Parsed generator configuration. * </p> */ protected MockConfig mockConfig; protected int errorsCount = 0; public void execute() throws MojoExecutionException { File f = getOutputJavaDirectory(); // Prepare output directory. if (!f.exists()) { f.mkdirs(); } mockConfig = readConfig(); projectClassLoader = createProjectClassLoader(); MockControlSource mockControl = new MockControlSource( getOutputJavaDirectory(), mockConfig.getMockController()); mockControl.printFileHeader(); for (Mock mock : (List<Mock>) mockConfig.getMocks()) { generateClass(mock); mockControl.printMockClass(mock.getName()); } if(errorsCount>0){ throw new MojoExecutionException( "Errors during mock classes grneration"); } mockControl.printFileFooter(); try { mockControl.writeClassFile(); } catch (IOException e) { throw new MojoExecutionException( "Error writing mock controller Java source", e); } addGeneratedSourcesToProject(); } protected abstract void addGeneratedSourcesToProject(); /** * <p class="changed_added_4_0"></p> * @return the outputJavaDirectory */ protected abstract File getOutputJavaDirectory(); /** * <p class="changed_added_4_0"></p> * @return the config */ protected abstract File getConfig(); /** * <p class="changed_added_4_0"></p> * @return the classpathElements */ protected abstract Collection<String> getClasspathElements(); private static final List<String> requiredSystemMethods = Arrays.asList("toString", "equals", "hashCode"); private static final List<String> systemMethods = Arrays.asList("getClass", "wait", "notify", "notifyAll"); /** * <p class="changed_added_4_0"> * Generate single Mock class * </p> * * @param mockConfig * @param mock */ protected void generateClass(Mock mock) throws MojoExecutionException { try { String className = mock.getClassName(); Class<?> baseClass = projectClassLoader.loadClass(className); String mockClassName = mock.getName(); if (null == mockClassName || mockClassName.length() == 0) { // Calculate mock class name. int indexOfDot = className.lastIndexOf('.'); mockClassName = mockConfig.getMockPackage() + "." + mockConfig.getClassPrefix() + className.substring(indexOfDot + 1); mock.setName(mockClassName); } MockJavaSource javaSource = new MockJavaSource(getOutputJavaDirectory(), mockClassName, className, mockConfig.getMockController()); javaSource.printFileHeader(mock.getPostConstruct()); List<Method> declaredMethods = getPublicMethods(baseClass); for (Method method : declaredMethods) { String name = method.getName(); if (!skipMethod(mock, name)) { javaSource.printMethod(method); } } javaSource.printFileFooter(mock.getCode()); javaSource.writeClassFile(); } catch (ClassNotFoundException e) { getLog().error("Base class for Mock not found", e); errorsCount++; } catch (IOException e) { getLog().error("Error writing Mock class source", e); errorsCount++; } } protected List<Method> getPublicMethods(Class<?> baseClass) { Method[] declaredMethods = baseClass.getMethods(); List<Method> publicMethods = new LinkedList<Method>(); for (Method method : declaredMethods) { List<Method> overridenPublicMethods = new ArrayList<Method>(); boolean visible = true; int modifiers = method.getModifiers(); // Do not generate non-abstract system , static, and final methods. if (requiredSystemMethods.contains(method.getName()) || (systemMethods.contains(method.getName()) && 0 == (modifiers&Modifier.ABSTRACT)) || 0 != (modifiers & (Modifier.STATIC|Modifier.FINAL))) { visible = false; } else { for (Method otherMethod : publicMethods) { if (method.getName().equals(otherMethod.getName()) && method != otherMethod) { // two different methods with same name, check arguments Class<?>[] methodParameters = method .getParameterTypes(); Class<?>[] otherMethodParameters = otherMethod .getParameterTypes(); if (Arrays.equals(methodParameters, otherMethodParameters)) { // these methods have same signatures, check their // classes. if (method.getDeclaringClass().isAssignableFrom( otherMethod.getDeclaringClass())) { if (!method.getDeclaringClass().equals(otherMethod.getDeclaringClass())) { // otherMetood overrides method, so we should // skip // first declaration. visible = false; break; } else { if (method.getReturnType().isAssignableFrom( otherMethod.getReturnType())) { //Java 5+ permits methods that return subtypes of parent's return type visible = false; break; } else { overridenPublicMethods.add(otherMethod); } } } else { overridenPublicMethods.add(otherMethod); } } } } } publicMethods.removeAll(overridenPublicMethods); if (visible) { publicMethods.add(method); } } return publicMethods; } @SuppressWarnings("unchecked") protected boolean skipMethod(Mock mock, String name) { for (MockMethod mockMethod : (List<MockMethod>) mock.getMethods()) { if (name.equals(mockMethod.getName()) && mockMethod.isExclude()) { return true; } } return false; } @SuppressWarnings("unchecked") protected ClassLoader createProjectClassLoader() { ClassLoader classLoader = null; try { Collection<String> classpathElements = getClasspathElements(); String outputDirectory = project.getBuild().getOutputDirectory(); URL[] urls = new URL[classpathElements.size() + 1]; int i = 0; urls[i++] = new File(outputDirectory).toURI().toURL(); for (Iterator<String> iter = classpathElements.iterator(); iter .hasNext();) { String element = iter.next(); urls[i++] = new File(element).toURI().toURL(); } classLoader = new URLClassLoader(urls); } catch (MalformedURLException e) { getLog().error("Bad URL in classpath", e); } return classLoader; } protected MockConfig readConfig() throws MojoExecutionException { // Read configuration FileInputStream configurationInput = null; try { MockConfigXpp3Reader reader = new MockConfigXpp3Reader(); configurationInput = new FileInputStream(getConfig()); MockConfig mockConfig = reader.read(configurationInput); return mockConfig; } catch (FileNotFoundException e) { throw new MojoExecutionException( "Configuration file does not exists:" + getConfig().getAbsolutePath()); } catch (IOException e) { throw new MojoExecutionException( "Error readind configuration file: " + getConfig().getAbsolutePath(), e); } catch (XmlPullParserException e) { throw new MojoExecutionException( "Error parsing configuration file: " + getConfig().getAbsolutePath(), e); } finally { if (null != configurationInput) { try { configurationInput.close(); } catch (IOException e) { // Ignore it. } } } } }