/* * 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); } } }