blob: b3c6f3e7b24d9cae4ab3ceda0d164111937a07dd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.sdk.internal.workspace.dto.formdata;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.scout.commons.holders.BooleanHolder;
import org.eclipse.scout.sdk.extensions.runtime.classes.IRuntimeClasses;
import org.eclipse.scout.sdk.internal.ScoutSdk;
import org.eclipse.scout.sdk.internal.workspace.dto.AbstractDtoTypeSourceBuilder;
import org.eclipse.scout.sdk.internal.workspace.dto.DtoUtility;
import org.eclipse.scout.sdk.sourcebuilder.SortedMemberKeyFactory;
import org.eclipse.scout.sdk.sourcebuilder.annotation.AnnotationSourceBuilderFactory;
import org.eclipse.scout.sdk.sourcebuilder.comment.CommentSourceBuilderFactory;
import org.eclipse.scout.sdk.sourcebuilder.method.IMethodBodySourceBuilder;
import org.eclipse.scout.sdk.sourcebuilder.method.IMethodSourceBuilder;
import org.eclipse.scout.sdk.sourcebuilder.method.MethodSourceBuilderFactory;
import org.eclipse.scout.sdk.util.NamingUtility;
import org.eclipse.scout.sdk.util.ScoutUtility;
import org.eclipse.scout.sdk.util.method.IMethodReturnExpressionRewrite;
import org.eclipse.scout.sdk.util.signature.IImportValidator;
import org.eclipse.scout.sdk.util.signature.SignatureCache;
import org.eclipse.scout.sdk.util.type.TypeUtility;
import org.eclipse.scout.sdk.util.typecache.ITypeHierarchy;
import org.eclipse.scout.sdk.workspace.dto.formdata.FormDataAnnotation;
import org.eclipse.scout.sdk.workspace.type.ScoutTypeUtility;
import org.eclipse.scout.sdk.workspace.type.validationrule.ValidationRuleMethod;
/**
* <h3>{@link FormDataTypeSourceBuilder}</h3>
*
* @author Andreas Hoegger
* @since 3.10.0 27.08.2013
*/
public class FormDataTypeSourceBuilder extends AbstractDtoTypeSourceBuilder {
private static final Pattern REGEX_STRING_LITERALS = Pattern.compile("\"+[^\"]+\"", Pattern.DOTALL);
private static final Pattern REGEX_CONSTRUCTOR_CALL = Pattern.compile("new\\s+[A-Za-z][a-zA-Z0-9_\\.]{0,200}\\s*\\([^\\(\\)]*\\)");
private FormDataAnnotation m_formDataAnnotation;
/**
* @param modelType
* @param elementName
*/
public FormDataTypeSourceBuilder(IType modelType, ITypeHierarchy modelLocalTypeHierarchy, String elementName, FormDataAnnotation formDataAnnotation, ICompilationUnit derivedCu, IProgressMonitor monitor) {
super(modelType, modelLocalTypeHierarchy, elementName, false, derivedCu, monitor);
m_formDataAnnotation = formDataAnnotation;
setup(monitor);
}
@Override
protected void createContent(IProgressMonitor monitor) {
super.createContent(monitor);
if (monitor.isCanceled()) {
return;
}
collectProperties(monitor);
if (monitor.isCanceled()) {
return;
}
try {
collectValidationRules(monitor);
if (monitor.isCanceled()) {
return;
}
}
catch (Exception t) {
ScoutSdk.logError("could not append validation rules to form field data '" + getModelType().getFullyQualifiedName() + "'.", t);
}
}
@Override
protected String computeSuperTypeSignature() throws JavaModelException {
String superTypeSignature = null;
if (ScoutTypeUtility.existsReplaceAnnotation(getModelType())) {
IType replacedType = getLocalTypeHierarchy().getSuperclass(getModelType());
IType replacedFormFieldDataType = DtoUtility.getFormDataType(replacedType, getLocalTypeHierarchy());
if (replacedFormFieldDataType != null) {
superTypeSignature = SignatureCache.createTypeSignature(replacedFormFieldDataType.getFullyQualifiedName());
}
addAnnotationSourceBuilder(AnnotationSourceBuilderFactory.createReplaceAnnotationBuilder());
}
if (superTypeSignature == null) {
superTypeSignature = DtoUtility.computeSuperTypeSignatureForFormData(getModelType(), getFormDataAnnotation(), getLocalTypeHierarchy());
}
return superTypeSignature;
}
protected void collectValidationRules(IProgressMonitor monitor) throws CoreException {
// validation rules
boolean replaceAnnotationPresent = ScoutTypeUtility.existsReplaceAnnotation(getModelType());
ITypeHierarchy hierarchy = getLocalTypeHierarchy();
if (hierarchy == null) {
hierarchy = TypeUtility.getSupertypeHierarchy(getModelType());
if (hierarchy == null) {
ScoutSdk.logError("Cannot collect validation rules for form data. Unable to create super type hierarchy for type '" + getModelType().getFullyQualifiedName() + "'.");
return;
}
}
// If the replace annotation is available, we have to check whether the replaced field is not associated to a form field data
boolean superTypeHasNoFormFieldData = false;
if (replaceAnnotationPresent) {
IType superType = hierarchy.getSuperclass(getModelType());
superTypeHasNoFormFieldData = DtoUtility.getFormDataType(superType, hierarchy) == null;
}
final List<ValidationRuleMethod> list = DtoUtility.getValidationRuleMethods(getModelType(), hierarchy, monitor);
if (monitor.isCanceled()) {
return;
}
if (list.size() > 0) {
for (Iterator<ValidationRuleMethod> it = list.iterator(); it.hasNext();) {
if (monitor.isCanceled()) {
return;
}
ValidationRuleMethod vm = it.next();
if (replaceAnnotationPresent) {
if (superTypeHasNoFormFieldData && vm.isSkipRule()) {
it.remove();
continue;
}
else if (!superTypeHasNoFormFieldData && !getModelType().equals(vm.getImplementedMethod().getDeclaringType())) {
// remove all validation rules that are not overridden by the replacement class
it.remove();
continue;
}
}
else if (vm.isSkipRule()) {
// class does not replace its super class. Hence remove all skipped validation rules
it.remove();
continue;
}
// skip validation rules having return value null or false
String simpleSourceCode = vm.getRuleReturnExpression().getReturnStatement(); // pre calculate a return statement without import validation. Is sufficient for the check that is done here.
if ("null".equals(simpleSourceCode) || "false".equals(simpleSourceCode)) {
if (replaceAnnotationPresent && !superTypeHasNoFormFieldData) {
// replace annotation is present and super type has already a form field data. Hence rule must be overridden in the generated method.
vm.setSkipRule(true);
}
else {
// no replace annotation is present. Hence just ignore the rule.
it.remove();
}
continue;
}
}
if (list.size() > 0) {
IMethodSourceBuilder initValidationRulesBuilder = MethodSourceBuilderFactory.createOverrideMethodSourceBuilder(this, "initValidationRules");
initValidationRulesBuilder.setMethodBodySourceBuilder(new IMethodBodySourceBuilder() {
@Override
public void createSource(IMethodSourceBuilder methodBuilder, StringBuilder source, String lineDelimiter, IJavaProject ownerProject, IImportValidator validator) throws CoreException {
source.append("super.initValidationRules(ruleMap);");
for (ValidationRuleMethod vm : list) {
try {
// filter out code that contains references to types that are not accessible from our bundle.
String generatedSourceCode = generateValidationRuleSourceCode(vm, ownerProject, validator);
if (!vm.isSkipRule() && generatedSourceCode == null) {
//add javadoc warning
String fqn = vm.getImplementedMethod().getDeclaringType().getFullyQualifiedName('.') + "#" + vm.getImplementedMethod().getElementName();
source.append("/**");
source.append(lineDelimiter);
source.append(" * XXX not processed ValidationRule(" + vm.getRuleName() + ")");
if (vm.getRuleReturnExpression() != null) {
source.append(lineDelimiter);
source.append(" * '" + vm.getRuleReturnExpression() + "' is not accessible from here.");
}
source.append(lineDelimiter);
source.append(" * at " + fqn);
source.append(lineDelimiter);
source.append("*/");
}
else {
String ruleDecl;
if (vm.getRuleField() != null) {
validator.getTypeName(SignatureCache.createTypeSignature(vm.getRuleField().getDeclaringType().getFullyQualifiedName())); // add to imports if necessary
ruleDecl = new StringBuilder(vm.getRuleField().getDeclaringType().getElementName()).append('.').append(vm.getRuleField().getElementName()).toString();
}
else {
String ruleName = vm.getRuleName();
ruleDecl = new StringBuilder(ruleName.length() + 2).append('"').append(ruleName).append('"').toString();
}
source.append(lineDelimiter);
if (vm.isSkipRule()) {
source.append("ruleMap.remove(");
source.append(ruleDecl);
source.append(");");
}
else {
source.append("ruleMap.put(");
source.append(ruleDecl);
source.append(", ");
source.append(generatedSourceCode);
source.append(");");
}
}
}
catch (Exception e) {
String fqn = vm.getImplementedMethod().getDeclaringType().getFullyQualifiedName() + "#" + vm.getImplementedMethod().getElementName();
ScoutSdk.logError("could not append rule " + vm.getRuleName() + " from method " + fqn + ".", e);
}
}
}
private String generateValidationRuleSourceCode(ValidationRuleMethod vrm, IJavaProject formDataProject, IImportValidator validator) throws CoreException {
if (vrm.getRuleReturnExpression() == null) {
return null;
}
final BooleanHolder rewritePossible = new BooleanHolder(true);
final Map<SimpleName, IJavaElement> referencedElements = vrm.getRuleReturnExpression().getReferencedElements();
String sourceSnippet = vrm.getRuleReturnExpression().getReturnStatement(validator, formDataProject, new IMethodReturnExpressionRewrite() {
@Override
public boolean rewriteElement(SimpleName node, IJavaElement element, IImportValidator v, IJavaProject classPath, StringBuffer buffer) {
if (element.getElementType() == IJavaElement.TYPE) {
if (node.getParent() instanceof QualifiedName) {
QualifiedName parent = (QualifiedName) node.getParent();
if (node == parent.getQualifier()) {
if (referencedElements.containsKey(parent.getName())) {
// type can be ignored because we will handle the second part of the qualified name
return true;
}
}
if (parent.getParent() instanceof QualifiedName) {
// we are not the leaf -> ignore
return true;
}
}
IType type = (IType) element;
ITypeHierarchy h = TypeUtility.getSupertypeHierarchy(type);
if (h.contains(TypeUtility.getType(IRuntimeClasses.IValueField))) {
String formDataFieldName = NamingUtility.ensureStartWithUpperCase(ScoutUtility.removeFieldSuffix(type.getElementName()));
buffer.append(formDataFieldName);
return true; // rewrite done
}
}
rewritePossible.setValue(false);
return false;
}
});
if (!rewritePossible.getValue()) {
// we could not rewrite the statement so that all references could be resolved in the context of the form data
return null;
}
if (sourceSnippet != null) {
// it is no reference to another form field and all types are accessible: check for foreign method calls
if (!REGEX_CONSTRUCTOR_CALL.matcher(sourceSnippet).matches()) { // constructors are allowed because the type is accessible (checked before)
String srcWithoutStrings = REGEX_STRING_LITERALS.matcher(sourceSnippet).replaceAll(""); // remove string literals
if (srcWithoutStrings.contains("(")) {
// if it still contains a bracket: skip the rule
return null;
}
}
}
return sourceSnippet;
}
});
initValidationRulesBuilder.setCommentSourceBuilder(CommentSourceBuilderFactory.createCustomCommentBuilder("list of derived validation rules."));
addSortedMethodSourceBuilder(SortedMemberKeyFactory.createMethodFormDataValidationKey(initValidationRulesBuilder), initValidationRulesBuilder);
}
}
}
public FormDataAnnotation getFormDataAnnotation() {
return m_formDataAnnotation;
}
}