/*
* Copyright (c) 2009, Dennis M. Sosnoski. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of
* JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.dataone.jibx.schema.codegen.extend;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.jibx.binding.model.ElementBase;
import org.jibx.schema.codegen.IClassHolder;
import org.jibx.schema.codegen.NameUtils;
import org.jibx.schema.codegen.extend.ClassDecorator;
import org.jibx.schema.codegen.extend.NameMatchDecoratorBase;
import org.jibx.util.NameUtilities;
/**
* Code generation decorator which modifies the setValue method if a generated class to check for nonempty value
*
* Supplying a 'fields' attribute to the class-decorator element with space delimited names of the class level fields to
* apply restrictions to.
*
* This Decorator is mutually exclusive with NonEmptyNoWhitespaceString800TypeDecorator when applied to a generated
* class
*
* @author waltz
*/
public class NonEmptyStringTypeDecorator extends FieldMatchDecoratorBase implements ClassDecorator {
private final ASTParser m_parser = ASTParser.newParser(AST.JLS3);
// easy way to generate a couple of fields that can be added to the class
private static final String s_classText = "class Gorph { \n"
+ "private final String NONEMPTY_REGEX = \"^[\\\\s]*[\\\\S][\\\\S+\\\\s+]*$\";\n"
+ "Pattern pattern = Pattern.compile(NONEMPTY_REGEX);\n}";
// where $0 is the description text, $1 is the field name, $2 is the value name with initial uppercase character,
// $3 is the type, and $4 is a cast if an untyped list is used, or empty if a typed list is used
private static final String s_methodBlockText = "class Gorph { \n"
+ "public newMethod() {"
+ "for (String value: list) {\n"
+ "Matcher nonWhitespaceMatcher = pattern.matcher(value);\n"
+ "if (! nonWhitespaceMatcher.find()) {\n"
+ "throw new UnsupportedType(\\\"000\\\", \\\" does not conform to specification of \\\" + holder.getFullName());\n"
+ "}\n"
+ "}\n"
+ "}\n";
/**
*
* Default constructor to initialize must call super to include default fields
*
*/
public NonEmptyStringTypeDecorator() {
super();
}
/**
* Replace all occurrences of one string with another in a buffer.
*
* @param match
* @param replace
* @param buff
*/
private static void replace(String match, String replace, StringBuffer buff) {
int base = 0;
while ((base = buff.indexOf(match, base)) >= 0) {
buff.replace(base, base + match.length(), replace);
}
}
/**
* Method called after completing code generation for the target class.
*
* @param binding
* @param holder
*/
public void finish(ElementBase binding, IClassHolder holder) {
if (matchName(holder.getName())) {
MethodDeclaration[] methodDeclaration = holder.getMethods();
for (int i = 0; i < methodDeclaration.length; ++i) {
if (methodDeclaration[i].isConstructor()) {
// find constructor methods that use setValue and add a throws clause
if (methodDeclaration[i].parameters().size() > 0) {
AST ast = methodDeclaration[i].getAST();
methodDeclaration[i].thrownExceptions().add(ast.newSimpleName("UnsupportedType"));
}
}
}
}
}
/**
* Method called before starting code generation for the target class.
*
* @param holder
*/
public void start(IClassHolder holder) {
if (matchName(holder.getName())) {
holder.addImport("java.util.regex.Pattern");
holder.addImport("java.util.regex.Matcher");
holder.addImport("org.dataone.service.exceptions.UnsupportedType");
// parse the ast source text into a compilation unit
// such that the initial fields may be extracted and
// and added to the class level fields
StringBuilder buff = new StringBuilder(s_classText);
m_parser.setSource(buff.toString().toCharArray());
CompilationUnit unit = (CompilationUnit) m_parser.createAST(null);
// add all methods from output tree to class under construction
TypeDeclaration typedecl = (TypeDeclaration) unit.types().get(0);
for (Iterator iter = typedecl.bodyDeclarations().iterator(); iter.hasNext();) {
ASTNode node = (ASTNode) iter.next();
// add in the fields that were generated from the s_classText
if (node instanceof FieldDeclaration) {
holder.addField((FieldDeclaration) node);
}
}
}
}
/**
* Method called after adding each data value to class.
*
* @param basename base name used for data value
* @param collect repeated value flag
* @param type value type (item value type, in the case of a repeated value)
* @param field actual field
* @param getmeth read access method (null
if a flag value)
* @param setmeth write access method (null
if a flag value)
* @param descript value description text
* @param holder
*/
public void valueAdded(String basename, boolean collect, String type, FieldDeclaration field,
MethodDeclaration getmeth, MethodDeclaration setmeth, String descript, IClassHolder holder) {
if (!collect && matchName(holder.getName()) && m_fields.contains(basename)) {
// transform the setmeth method
Block valueBlockBody = setmeth.getBody();
AST ast = valueBlockBody.getAST();
setmeth.thrownExceptions().add(ast.newSimpleName("UnsupportedType"));
Block newMethodBlockBody = ast.newBlock();
/*
* Matcher nonWhitespaceMatcher = pattern.matcher(value);
if (nonWhitespaceMatcher.find()) {
this.value = value;
} else {
throw new UnsupportedType("000", "some exception");
}
*/
// Matcher nonWhitespaceMatcher = pattern.matcher(value);
VariableDeclarationFragment nonWhitespaceMatcherFragment = ast.newVariableDeclarationFragment();
nonWhitespaceMatcherFragment.setName(ast.newSimpleName("nonWhitespaceMatcher"));
VariableDeclarationStatement nonWhitespaceMatcherDeclarationStatement = ast.newVariableDeclarationStatement(nonWhitespaceMatcherFragment);
nonWhitespaceMatcherDeclarationStatement.setType(ast.newSimpleType(ast.newSimpleName("Matcher")));
newMethodBlockBody.statements().add(nonWhitespaceMatcherDeclarationStatement);
MethodInvocation patternMatcherMethodInvocation = ast.newMethodInvocation();
patternMatcherMethodInvocation.setExpression(ast.newSimpleName("pattern"));
patternMatcherMethodInvocation.setName(ast.newSimpleName("matcher"));
patternMatcherMethodInvocation.arguments().add(ast.newSimpleName(basename));
nonWhitespaceMatcherFragment.setInitializer(patternMatcherMethodInvocation);
//if (nonWhitespaceMatcher.find()) {
IfStatement ifStatement = ast.newIfStatement();
Block thenBlock = ast.newBlock();
ifStatement.setThenStatement(thenBlock);
//nonWhitespaceMatcher.find()
MethodInvocation findPatternMethodMatch = ast.newMethodInvocation();
findPatternMethodMatch.setExpression(ast.newSimpleName("nonWhitespaceMatcher"));
findPatternMethodMatch.setName(ast.newSimpleName("find"));
ifStatement.setExpression(findPatternMethodMatch);
newMethodBlockBody.statements().add(ifStatement);
// this.value = value;
Assignment valueAssignment = ast.newAssignment();
valueAssignment.setOperator(Assignment.Operator.ASSIGN);
FieldAccess valueField = ast.newFieldAccess();
valueField.setExpression(ast.newThisExpression());
valueField.setName(ast.newSimpleName(basename));
valueAssignment.setLeftHandSide(valueField);
valueAssignment.setRightHandSide(ast.newSimpleName(basename));
thenBlock.statements().add(ast.newExpressionStatement(valueAssignment));
Block elseBlock = ast.newBlock();
ifStatement.setElseStatement(elseBlock);
ThrowStatement throwUnsupportedType = ast.newThrowStatement();
ClassInstanceCreation unsupportedTypeClass = ast.newClassInstanceCreation();
throwUnsupportedType.setExpression(unsupportedTypeClass);
SimpleType unsupportedType = ast.newSimpleType(ast.newSimpleName("UnsupportedType"));
elseBlock.statements().add(throwUnsupportedType);
unsupportedTypeClass.setType(unsupportedType);
StringLiteral detailCodeLiteral = ast.newStringLiteral();
// there will be no way of knowing the detail code since
// we don't know from which rest method it will be called in
detailCodeLiteral.setLiteralValue("000");
InfixExpression exceptionMessageExpression = ast.newInfixExpression();
exceptionMessageExpression.setOperator(InfixExpression.Operator.PLUS);
exceptionMessageExpression.setLeftOperand(ast.newSimpleName(basename));
StringLiteral nonConformanceliteral = ast.newStringLiteral();
nonConformanceliteral.setLiteralValue(" does not conform to specification of " + holder.getFullName());
exceptionMessageExpression.setRightOperand(nonConformanceliteral);
unsupportedTypeClass.arguments().add(detailCodeLiteral);
unsupportedTypeClass.arguments().add(exceptionMessageExpression);
// delete the old method body and set the new method body
valueBlockBody.delete();
setmeth.setBody(newMethodBlockBody);
} else if (collect && matchName(holder.getName()) && m_fields.contains(basename)) {
Block currentBlockBody = setmeth.getBody();
AST ast = currentBlockBody.getAST();
setmeth.thrownExceptions().add(ast.newSimpleName("UnsupportedType"));
String listParamName = "list";
FieldDeclaration[] fieldDeclarationArray = holder.getFields();
List setMethodParameters= setmeth.parameters();
for (SingleVariableDeclaration setMethodParam: setMethodParameters) {
if (setMethodParam.getType().isArrayType()) {
listParamName = setMethodParam.getName().getFullyQualifiedName();
}
}
Block newBody = ast.newBlock();
/*
for (int i = 0; i < list.size(); ++i) {
Matcher nonWhitespaceMatcher = pattern.matcher(list.get[i]);
if (!nonWhitespaceMatcher.find()) {
throw new UnsupportedType("000", "some exception");
}
}
*/
// int i = 0
VariableDeclarationFragment forInitializer= ast.newVariableDeclarationFragment();
forInitializer.setName(ast.newSimpleName("i"));
VariableDeclarationExpression intIExpression = ast.newVariableDeclarationExpression(forInitializer);
intIExpression.setType(ast.newPrimitiveType(PrimitiveType.INT));
forInitializer.setInitializer(ast.newNumberLiteral("1"));
// i < list.size()
InfixExpression forValueLengthExpression = ast.newInfixExpression();
forValueLengthExpression.setOperator(InfixExpression.Operator.LESS);
forValueLengthExpression.setLeftOperand(ast.newSimpleName("i"));
MethodInvocation listSizeMethodInvocation = ast.newMethodInvocation();
listSizeMethodInvocation.setExpression(ast.newSimpleName("list"));
listSizeMethodInvocation.setName(ast.newSimpleName("size"));
forValueLengthExpression.setRightOperand(listSizeMethodInvocation);
// ++i
PrefixExpression forUnaryIncrementExpression = ast.newPrefixExpression();
forUnaryIncrementExpression.setOperator(PrefixExpression.Operator.INCREMENT);
forUnaryIncrementExpression.setOperand(ast.newSimpleName("i"));
ForStatement forExpressionStatement = ast.newForStatement();
newBody.statements().add(forExpressionStatement);
forExpressionStatement.initializers().add(intIExpression);
forExpressionStatement.setExpression(forValueLengthExpression);
forExpressionStatement.updaters().add(forUnaryIncrementExpression);
Block forStatementBody = ast.newBlock();
forExpressionStatement.setBody(forStatementBody);
// Matcher nonWhitespaceMatcher = pattern.matcher(value);
VariableDeclarationFragment nonWhitespaceMatcherFragment = ast.newVariableDeclarationFragment();
nonWhitespaceMatcherFragment.setName(ast.newSimpleName("nonWhitespaceMatcher"));
VariableDeclarationStatement nonWhitespaceMatcherDeclarationStatement = ast.newVariableDeclarationStatement(nonWhitespaceMatcherFragment);
nonWhitespaceMatcherDeclarationStatement.setType(ast.newSimpleType(ast.newSimpleName("Matcher")));
forStatementBody.statements().add(nonWhitespaceMatcherDeclarationStatement);
MethodInvocation patternMatcherMethodInvocation = ast.newMethodInvocation();
patternMatcherMethodInvocation.setExpression(ast.newSimpleName("pattern"));
patternMatcherMethodInvocation.setName(ast.newSimpleName("matcher"));
MethodInvocation listGetMethodInvocation = ast.newMethodInvocation();
listGetMethodInvocation.setExpression(ast.newSimpleName(listParamName));
listGetMethodInvocation.setName(ast.newSimpleName("get"));
listGetMethodInvocation.arguments().add(ast.newSimpleName("i"));
patternMatcherMethodInvocation.arguments().add(listGetMethodInvocation);
nonWhitespaceMatcherFragment.setInitializer(patternMatcherMethodInvocation);
// if (!nonWhitespaceMatcher.find()) {
IfStatement ifStatement = ast.newIfStatement();
Block thenBlock = ast.newBlock();
ifStatement.setThenStatement(thenBlock);
PrefixExpression notNonEmptyFindExpression = ast.newPrefixExpression();
notNonEmptyFindExpression.setOperator(PrefixExpression.Operator.NOT);
ifStatement.setExpression(notNonEmptyFindExpression);
MethodInvocation findPatternMethodMatch = ast.newMethodInvocation();
findPatternMethodMatch.setExpression(ast.newSimpleName("nonWhitespaceMatcher"));
findPatternMethodMatch.setName(ast.newSimpleName("find"));
notNonEmptyFindExpression.setOperand(findPatternMethodMatch);
forStatementBody.statements().add(ifStatement);
// throw new UnsupportedType("000", "some exception");
ThrowStatement throwUnsupportedType = ast.newThrowStatement();
ClassInstanceCreation unsupportedTypeClass = ast.newClassInstanceCreation();
throwUnsupportedType.setExpression(unsupportedTypeClass);
SimpleType unsupportedType = ast.newSimpleType(ast.newSimpleName("UnsupportedType"));
thenBlock.statements().add(throwUnsupportedType);
unsupportedTypeClass.setType(unsupportedType);
StringLiteral detailCodeLiteral = ast.newStringLiteral();
// there will be no way of knowing the detail code since
// we don't know from which rest method it will be called in
detailCodeLiteral.setLiteralValue("000");
// InfixExpression exceptionMessageExpression = ast.newInfixExpression();
// exceptionMessageExpression.setOperator(InfixExpression.Operator.PLUS);
StringLiteral nonConformanceliteral = ast.newStringLiteral();
nonConformanceliteral.setLiteralValue(basename + " does not conform to specification of " + holder.getFullName());
// exceptionMessageExpression.setRightOperand(nonConformanceliteral);
unsupportedTypeClass.arguments().add(detailCodeLiteral);
unsupportedTypeClass.arguments().add(nonConformanceliteral);
// this.value = value;
Assignment valueAssignment = ast.newAssignment();
valueAssignment.setOperator(Assignment.Operator.ASSIGN);
FieldAccess valueField = ast.newFieldAccess();
valueField.setExpression(ast.newThisExpression());
valueField.setName(ast.newSimpleName(basename));
valueAssignment.setLeftHandSide(valueField);
valueAssignment.setRightHandSide(ast.newSimpleName(listParamName));
newBody.statements().add(ast.newExpressionStatement(valueAssignment));
setmeth.getBody().delete();
setmeth.setBody(newBody);
}
}
}