/*
* 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.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
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.NumberLiteral;
import org.eclipse.jdt.core.dom.SimpleType;
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.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 to check for a maximum length of 800 characters
* and a nonempty value that contains no whitespace
*
* 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 NonEmptyTypeDecorator
* when applied to a generated class
*
* @author waltz
*/
public class NonEmptyNoWhitespaceString800TypeDecorator extends FieldMatchDecoratorBase implements ClassDecorator {
/**
* Parser instance used by class.
*/
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 NONWHITESPACE_REGEX = \"^\\\\S+$\";\n"
+ "Pattern pattern = Pattern.compile(NONWHITESPACE_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
/**
*
* Default constructor to initialize m_fields with the default String field 'value' that is used in Dataone. Note if
* this were to be generalized, then 'string' would also be added.
*
*/
public NonEmptyNoWhitespaceString800TypeDecorator() {
super();
}
/**
* 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 setValue 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() && (value.length <= 800)) {
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() && (value.length() <= 800)) {
IfStatement ifStatement = ast.newIfStatement();
Block thenBlock = ast.newBlock();
ifStatement.setThenStatement(thenBlock);
InfixExpression ifValueExpression = ast.newInfixExpression();
ifValueExpression.setOperator(InfixExpression.Operator.CONDITIONAL_AND);
//nonWhitespaceMatcher.find()
MethodInvocation findPatternMethodMatch = ast.newMethodInvocation();
findPatternMethodMatch.setExpression(ast.newSimpleName("nonWhitespaceMatcher"));
findPatternMethodMatch.setName(ast.newSimpleName("find"));
//value.length <= 800
InfixExpression valueLengthExpression = ast.newInfixExpression();
valueLengthExpression.setOperator(InfixExpression.Operator.LESS_EQUALS);
MethodInvocation valueLengthMethodInvocation = ast.newMethodInvocation();
valueLengthMethodInvocation.setExpression(ast.newSimpleName(basename));
valueLengthMethodInvocation.setName(ast.newSimpleName("length"));
valueLengthExpression.setLeftOperand(valueLengthMethodInvocation);
NumberLiteral maxSizeLength = ast.newNumberLiteral();
maxSizeLength.setToken("800");
valueLengthExpression.setRightOperand(maxSizeLength);
ifValueExpression.setLeftOperand(findPatternMethodMatch);
ifValueExpression.setRightOperand(valueLengthExpression);
ifStatement.setExpression(ifValueExpression);
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);
}
}
}