/* * Copyright 2012-2013 iDA MediaFoundry (www.ida-mediafoundry.be) * * 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. */ package be.idamediafoundry.sofa.livecycle.dsc.util; import java.io.File; import java.text.BreakIterator; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.OperationType; import com.thoughtworks.qdox.JavaDocBuilder; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaPackage; import com.thoughtworks.qdox.model.JavaParameter; import com.thoughtworks.qdox.model.Type; import org.apache.maven.plugin.logging.Log; /** * Extracts component info from java classes using the QDox framework. * * @author Mike Seghers * */ public abstract class AbstractQDoxComponentInfoExtractor implements ComponentInfoExtractor<JavaClass, JavaMethod, JavaMethod, JavaParameter, Type> { protected static final String DEFAULT_OUT_PARAM_NAME = "out"; protected static final String RETURN_TAG = "return"; protected static final String PARAM_TAG = "param"; private JavaDocBuilder builder; private Log log; public AbstractQDoxComponentInfoExtractor(String sourcePath, Log log) { this.builder = new JavaDocBuilder(); this.builder.addSourceTree(new File(sourcePath)); this.log = log; } final public List<JavaClass> getServicesInfo() { List<JavaClass> result = new ArrayList<JavaClass>(); JavaPackage[] packages = builder.getPackages(); for (JavaPackage javaPackage : packages) { JavaClass[] classes = javaPackage.getClasses(); for (JavaClass javaClass : classes) { if (acceptAsService(javaClass)) { result.add(javaClass); } } } return result; } final public List<JavaMethod> getOperationsInfo(JavaClass serviceInfo) { List<JavaMethod> result = new ArrayList<JavaMethod>(); JavaMethod[] methods = serviceInfo.getMethods(); for (JavaMethod javaMethod : methods) { if (acceptAsOperation(javaMethod)) { result.add(javaMethod); } } return result; } final public List<JavaMethod> getConfigParametersInfo(JavaClass serviceInfo) { List<JavaMethod> result = new ArrayList<JavaMethod>(); JavaMethod[] methods = serviceInfo.getMethods(); for (JavaMethod javaMethod : methods) { if (acceptAsConfigParameter(javaMethod)) { result.add(javaMethod); } } return result; } final public List<JavaParameter> getOperationInputParameters( JavaMethod operationInfo) { List<JavaParameter> result = new ArrayList<JavaParameter>(); JavaParameter[] parameters = operationInfo.getParameters(); for (JavaParameter javaParameter : parameters) { result.add(javaParameter); } return result; } final public List<Type> getOperationFaults(JavaMethod operationInfo) { List<Type> result = new ArrayList<Type>(); Type[] exceptions = operationInfo.getExceptions(); for (Type exception : exceptions) { result.add(exception); } return result; } public abstract boolean acceptAsService(JavaClass javaClass); public abstract boolean acceptAsOperation(JavaMethod javaMethod); public abstract boolean acceptAsConfigParameter(JavaMethod javaMethod); /** * Generate the operation name, method and title attributes. This method * will check for duplicates (overloaded methods) and generate a different * method name for these overloaded methods, also setting the method * attribute in the process (otherwise not needed). If the method seems to * be overloaded, then the operationName tag in the javadoc is looked up. If * it does not exist, a long name will be generated using the concatenated * method name and parameter names and their types. * * @param operationList * the operation list to check for overloaded methods * @param javaMethod * the java method * @param operation * the operation */ final protected void generateOperationNameMethodTitle( final List<String> existingOperationNames, final JavaMethod javaMethod, final OperationType operation, String suggestedName) { JavaParameter[] parameters = javaMethod.getParameters(); String methodName = javaMethod.getName(); String operationName; if (existingOperationNames.contains(methodName)) { // An overloaded method has been found, we will need to generate a // name // Let's see if the developer specified his preference if (StringUtils.isNotBlank(suggestedName)) { // Yes, he did! if (existingOperationNames.contains(suggestedName)) { throw new RuntimeException( "Could not generate component XML, the method " + methodName + " in class " + javaMethod.getParentClass().getName() + " has no unique operation name, please check your definition and make sure you specify a unique name"); } operationName = suggestedName; } else { // Generate one, using the parameter names and types StringBuilder generated = new StringBuilder(methodName); generated.append("With"); for (JavaParameter javaParameter : parameters) { generated.append(StringUtils.capitalize(javaParameter .getName())); generated.append("As"); generated.append(StringUtils.capitalize(javaParameter .getType().getJavaClass().getName())); } operationName = generated.toString(); if (existingOperationNames.contains(operationName)) { throw new RuntimeException( "Could not generate component XML, the system could not generate a unique operation name for method " + methodName + " in class " + javaMethod.getParentClass().getName() + ", please check your definition and make sure you specify a unique name"); } } operation.setMethod(methodName); } else { if (StringUtils.isNotBlank(suggestedName)) { operationName = suggestedName; } else { operationName = methodName; } } operation.setName(operationName); operation.setTitle(generateTitle(operationName)); } /** * Generate an appropriate title for an element holding "title". The title * of an element is shown in the workbench as label for the operation, * configuration, input, output and fault elements. This method will make a * sentence of a camel cased string, transform it to lower case and finally * capitalize the first letter again. * * @param base * the camel cased string * @return the sentence */ final protected String generateTitle(final String base) { return StringUtils.capitalize(StringUtils.join( StringUtils.splitByCharacterTypeCamelCase(base), ' ') .toLowerCase()); } /** * Get the fully qualified name from a Type object. If the type is a * generic, java.lang.Object is returned. * * @param type * the type * @return the fully qualified name */ final protected String getFullyQualifiedJavaType(final Type type) { String strType; if (type.isResolved()) { strType = type.getFullyQualifiedName(); } else { strType = "java.lang.Object"; } return strType; } /** * Get the comments on a doc tag in a map (key is the first value in the doc * tag, the rest is the value). * * @param javaMethod * the java method in which to look for the tag * @param tagName * the name of the tag * @return a map of comments for a certain tag name */ final protected Map<String, String> getCommentMapForTag( final JavaMethod javaMethod, final String tagName) { DocletTag[] paramTags = javaMethod.getTagsByName(tagName); Map<String, String> paramTagMap = new HashMap<String, String>(); for (DocletTag docletTag : paramTags) { String value = docletTag.getValue(); paramTagMap.put(docletTag.getParameters()[0], value.substring(value.indexOf(' ') + 1)); } return paramTagMap; } final protected Log getLog() { return log; } final protected String getFirstSentence(String text) { String result = text; if (text != null) { BreakIterator iterator = BreakIterator.getSentenceInstance(); iterator.setText(text); int start = iterator.first(); int end = iterator.next(); if (end != BreakIterator.DONE) { result = text.substring(start, end).trim(); } } return result; } }