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