package com.francetelecom.rd.stubs.engine;
/*
* #%L
* Matos
* $Id:$
* $HeadURL:$
* %%
* Copyright (C) 2008 - 2014 Orange SA
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
/**
* Handle the generation of XML rule files.
* @author Pierre Cregut
*
*/
public class RuleGenerator {
final static String ARGS_KIND = "args";
final static String USE_KIND = "use";
final static String RETURN_KIND = "return";
final static String FIELD_KIND = "field";
final private PrintStream out;
final private PrintStream side;
final private ReflexUtil rf;
final private Hierarchy hierarchy;
RuleGenerator(PrintStream out, PrintStream side, ReflexUtil rf, Hierarchy hierarchy) {
this.rf = rf;
this.out = out;
this.side = side;
this.hierarchy = hierarchy;
}
void generateForClass(Class<?> clazz) {
String className = rf.restoreString(clazz.getName());
Annotation [] annots = clazz.getAnnotations();
Annotation dump_annot = rf.findAnnotation(EngineConstant.DUMP_HIERARCHY_ANNOT, annots);
if (dump_annot != null) {
Boolean onlyReal = (Boolean) ReflexUtil.getAnnotationField(dump_annot, EngineConstant.NO_ABSTRACT_FIELD);
List <Class <?>> implementers = hierarchy.getRecursiveSubclasses(clazz);
out.print("<option name=\"");
out.print(ReflexUtil.getStringValue(dump_annot));
out.print("\" value=\"");
boolean first = true;
for(Class <?> imp: implementers) {
int mod = imp.getModifiers();
if(onlyReal && Modifier.isAbstract(mod)) continue;
if(first) first = false;
else out.print(",");
out.print(type(imp));
}
out.print("\"/>");
}
for(Method m : clazz.getDeclaredMethods()) {
boolean isStatic = Modifier.isStatic(m.getModifiers());
generateForMethod(className, m.getName(), m.getReturnType(), m.getParameterTypes(), m.getAnnotations(),isStatic);
}
for(Constructor <? > co : clazz.getConstructors()) {
generateForMethod(className, "<init>", void.class, co.getParameterTypes(), co.getAnnotations(), false);
}
for(Field field : clazz.getDeclaredFields()) {
generateForField(className, field.getName(), field.getType(), field.getAnnotations());
}
/* Should be done as part of direct subclass treatment (declared in hierarchy).
for(Class <?> c : clazz.getDeclaredClasses()) {
generateForClass(c);
}
*/
}
private void generateForMethod(String className, String methName, Class<?> returnType,
Class<?>[] parameterTypes, Annotation[] annotations, boolean isStatic) {
List <Annotation> listAnnot = rf.findAnnotations(EngineConstant.ARG_RULE_ANNOT, EngineConstant.ARG_RULE_ARRAY_ANNOT, annotations);
for(Annotation annot : listAnnot) {
dumpReportRule(annot, className, methName, returnType, parameterTypes);
dumpRule(annot, ARGS_KIND, className, methName, returnType, parameterTypes, isStatic);
}
Annotation annot = rf.findAnnotation(EngineConstant.USE_RULE_ANNOT, annotations);
if (annot != null) {
dumpUseReportRule(annot, className, methName, returnType, parameterTypes);
dumpRule(annot, USE_KIND, className, methName, returnType, parameterTypes, isStatic);
}
annot = rf.findAnnotation(EngineConstant.RETURN_RULE_ANNOT, annotations);
if (annot != null) {
dumpRule(annot, RETURN_KIND, className, methName, returnType, parameterTypes, isStatic);
}
}
private void dumpRule(Annotation annot, String ruleKind, String className, String methName,
Class <?> returnType, Class <?> [] parameterTypes, boolean isStatic) {
String ruleName = ReflexUtil.getStringValue(annot);
String reportName = (String) ReflexUtil.getAnnotationField(annot, EngineConstant.REPORT_FIELD);
if (side != null) {
String ref = ruleKind.equals(RETURN_KIND) ? "returnRef" : "callRef";
side.println("<" + ref + " name=\"" + ruleName + "\"/>");
}
if (reportName.equals("-")) {
reportName = "R-" + ruleName;
}
out.println("<rule name=\"" + ruleName + "\">");
out.println(" <" + ruleKind + " class=\"" + className + "\"");
out.print(" signature=\"");
signature(methName, returnType, parameterTypes);
out.println("\"");
out.println(" report=\""+ reportName + "\">");
if (ruleKind.equals(ARGS_KIND)) {
int [] args = (int []) ReflexUtil.getAnnotationField(annot, EngineConstant.ARGS_FIELD);
for (int arg : args) {
if (arg == -1) {
if (returnType.equals(void.class))
System.err.println("Rule " + ruleName + " is on a method without result.");
} else if (arg == 0) {
if (isStatic)
System.err.println("Rule " + ruleName + " is on a static method.");
} else if (arg < 0 || arg > parameterTypes.length) {
System.err.println("Rule " + ruleName + " has no argument " + arg + ".");
}
out.println(" <argument position=\"" + arg + "\"/>");
}
}
out.println(" </" + ruleKind + ">");
out.println("</rule>");
}
private void dumpUseReportRule(Annotation annot, String className, String methName,
Class <?> returnType, Class <?> [] parameterTypes) {
String ruleName = ReflexUtil.getStringValue(annot);
out.println("<report name=\"R-" + ruleName + "\">");
out.println(" <forbiddenMethod silent=\"true\"><h4>%nA - " + ruleName + "</h4>%p</forbiddenMethod>");
out.println("</report>");
}
private void dumpReportRule(Annotation annot, String className, String methName,
Class <?> returnType, Class <?> [] parameterTypes) {
String ruleName = ReflexUtil.getStringValue(annot);
String reportName = (String) ReflexUtil.getAnnotationField(annot, EngineConstant.REPORT_FIELD);
int [] args = null;
args = (int []) ReflexUtil.getAnnotationField(annot, EngineConstant.ARGS_FIELD);
if (args != null) {
for(int i = 0; i < args.length; i++) {
out.println("<report name=\"" + ruleName + "-" + i + "\">");
String argName = (args[i] < 1) ? (args[i] == 0 ? "Base" : "Result") : ("Argument " + args[i]);
out.println(" <message><tr><td class=\"head\">" + argName + ":</td><td class=\"result\">%r</td></tr>
</message>");
out.println("</report>");
}
}
out.println("<report name=\"H1-" + ruleName + "\">");
out.println(" <message unique=\"title\"><h4>" + ruleName + "</h4>
</message>");
out.println("</report>");
out.println("<report name=\"H2-" + ruleName + "\">");
out.println(" <message><tr><td colspan=\"2\" class=\"result\">%nA</td></tr>
<tr><td class=\"head\">Caller:</td><td class=\"result\"> %c.%m</td></tr>
</message>");
out.println("</report>");
if (reportName.equals("-")) {
reportName = "R-" + ruleName;
}
out.println("<report name=\"" + reportName + "\">");
out.println(" <conjunction>");
out.println(" <ref name=\"H1-" + ruleName + "\"/>");
out.println(" <ref name=\"H-SEP\"/>");
out.println(" <ref name=\"OTABLE\"/>");
out.println(" <ref name=\"H2-" + ruleName + "\"/>");
if (args != null) {
for(int i = 0; i < args.length; i++) {
out.println(" <ref name=\"" + ruleName + "-"
+ i + "\" pos=\"" + i + "\"/>");
}
}
out.println(" <ref name=\"CTABLE\"/>");
out.println(" </conjunction>");
out.println("</report>");
}
private String type(Class<?> c) {
return escape(rf.restoreString(ReflexUtil.readableSootName(c)));
}
private void signature(String methName, Class<?> returnType,
Class<?>[] parameterTypes) {
out.print(type(returnType));
out.print(" ");
out.print(escape(methName));
boolean first = true;
out.print("(");
for(Class<?> paramType : parameterTypes) {
if (first) first = false;
else out.print(",");
out.print(type(paramType));
}
out.print(")");
}
private void generateForField(String className, String fieldName, Class<?> type,
Annotation[] annotations) {
Annotation annot = rf.findAnnotation(EngineConstant.FIELD_RULE_ANNOT, annotations);
if (annot == null) return;
String ruleName = ReflexUtil.getStringValue(annot);
String reportName = (String) ReflexUtil.getAnnotationField(annot, EngineConstant.REPORT_FIELD);
if (side != null) {
side.println("<fieldRef name=\"" + ruleName + "\"/>");
}
if (reportName == null || reportName.equals("-")) { reportName = "R-" + ruleName; }
out.println("<report name=\"" + reportName + "\">");
out.println(" <message>");
out.println(" <h4>Field " + ruleName + "</h4>");
out.println(" <table class=\"result\"><tr><td colspan=\"2\" class=\"result\">%nA</td></tr>
<tr><td class=\"head\">Values:</td><td class=\"result\">%r</td></tr></table>
");
out.println(" </message>");
out.println("</report>");
out.println("<rule name=\"" + ruleName + "\">");
out.println(" <field class=\"" + className + "\"");
out.print(" type=\"");
out.print(type(type));
out.print(" ");
out.print(escape(fieldName));
out.println("\"");
out.println(" report=\""+ reportName + "\">");
out.println(" </field>");
out.println("</rule>");
}
/**
* Generate the xml rule file.
* @param file the name of the file
* @param rf the context utility
* @param hierarchy a hierarchy of observed classes
* @throws IOException
*/
public static void generate(String file, String fileStruct, ReflexUtil rf,
Hierarchy hierarchy) throws IOException {
PrintStream out = new PrintStream(new FileOutputStream(file));
out.println("<matos>");
PrintStream side = (fileStruct == null) ? null : new PrintStream(new FileOutputStream(fileStruct));
if (side != null) side.println("<matos>");
RuleGenerator generator = new RuleGenerator(out, side, rf,hierarchy);
// out.println("<?xml version='1.0' encoding='utf-8'?>");
out.println("<report name=\"H-SEP\">");
out.println(" <message unique=\"-title\"><p></p></message>");
out.println("</report>");
out.println("<report name=\"OTABLE\">");
out.println(" <message><table class=\"result\"></message>");
out.println("</report>");
out.println("<report name=\"CTABLE\">");
out.println(" <message></table></message>");
out.println("</report>");
for(Class <?> clazz : hierarchy.getContents()) {
generator.generateForClass(clazz);
}
out.println("</matos>");
out.close();
if (side != null) {
side.println("</matos>");
side.close();
}
}
/**
* Make all dangerous characters in a string inert.
* @param s
* @return
*/
public static String escape(String s) {
if (s==null) return "";
if (needsEscape(s))
return s.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">")
.replaceAll("\"",""").replaceAll("\n","<br>");
else return s;
}
/**
* Check if it contains any of the characters to replace.
* @param s
* @return
*/
public static boolean needsEscape(String s) {
int l = s.length();
for(int i=0; i< l; i++) {
switch(s.charAt(i)) {
case '<': case '>': case '&': case '"': case '\'': case '\n': return true;
}
}
return false;
}
}