org.sdmlib.storyboards.Storyboard Maven / Gradle / Ivy
Show all versions of SDMLib Show documentation
/*
Copyright (c) 2014 zuendorf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.sdmlib.storyboards;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import org.junit.Assert;
import org.sdmlib.CGUtil;
import org.sdmlib.StrUtil;
import org.sdmlib.codegen.LocalVarTableEntry;
import org.sdmlib.codegen.Parser;
import org.sdmlib.codegen.StatementEntry;
import org.sdmlib.codegen.SymTabEntry;
import org.sdmlib.doc.DocEnvironment;
import org.sdmlib.doc.GraphFactory;
import org.sdmlib.doc.interfaze.Adapter.GuiAdapter;
import org.sdmlib.models.SDMLibIdMap;
import org.sdmlib.models.classes.ClassModel;
import org.sdmlib.models.classes.logic.GenClazzEntity;
import org.sdmlib.models.modelsets.ModelSet;
import org.sdmlib.models.objects.GenericGraph;
import org.sdmlib.models.objects.GenericObject;
import org.sdmlib.models.objects.util.GenericObjectSet;
import org.sdmlib.models.pattern.Pattern;
import org.sdmlib.models.pattern.PatternObject;
import org.sdmlib.serialization.EntityFactory;
import org.sdmlib.serialization.PropertyChangeInterface;
import org.sdmlib.storyboards.util.StoryboardStepSet;
import de.uniks.networkparser.Filter;
import de.uniks.networkparser.IdMap;
import de.uniks.networkparser.graph.Clazz;
import de.uniks.networkparser.interfaces.SendableEntityCreator;
import de.uniks.networkparser.interfaces.UpdateListener;
import de.uniks.networkparser.json.JsonArray;
import de.uniks.networkparser.IdMap;
import de.uniks.networkparser.list.SimpleKeyValueList;
import de.uniks.networkparser.interfaces.SendableEntity;
import org.sdmlib.storyboards.StoryboardWall;
import org.sdmlib.storyboards.StoryboardStep;
/**
* A Storyboard collects entries for the generation of an html page from e.g. a JUnit test.
* This html page will be named like the story, i.e. like the method that created the Storyboard.
* It will be added to the refs.html and thus become part of the index.html.
* All these html files are stored in an directory "doc" located in the project root directory.
*
* @see #dumpHTML()
* @see SDMLib Storyboards
* @see ProjectBoard.java
*/
public class Storyboard implements PropertyChangeInterface, SendableEntity
{
public static final String PROPERTY_STEPDONECOUNTER = "stepDoneCounter";
public static final String PROPERTY_STEPCOUNTER = "stepCounter";
public static final String PROPERTY_MODELROOTDIR = "modelRootDir";
public static final String PROPERTY_ROOTDIR = "rootDir";
public static final String PROPERTY_WALL = "wall";
public static final String PROPERTY_STORYBOARDSTEPS = "storyboardSteps";
public static final String MODELING = "modeling";
public static final String ACTIVE = "active";
public static final String DONE = "done";
public static final String IMPLEMENTATION = "implementation";
public static final String BACKLOG = "backlog";
private String name = "";
private GuiAdapter adapter;
private String javaTestFileName = "";
private JsonArray largestJsonArray = null;
private Object largestRoot = null;
private int stepDoneCounter;
private int stepCounter;
private String modelRootDir = null;
private String rootDir = null;
// private StoryboardWall wall = null;
private StoryboardStepSet storyboardSteps = null;
private int codeStartLineNumber = -1;
private ByteArrayOutputStream systemOutRecorder;
private IdMap jsonIdMap = null;
private LinkedHashMap iconMap = new LinkedHashMap();
public String getJavaTestFileName()
{
return javaTestFileName;
}
public void setJavaTestFileName(String javaTestFileName)
{
this.javaTestFileName = javaTestFileName;
}
public Storyboard withJsonIdMap(IdMap jsonIdMap)
{
this.jsonIdMap = jsonIdMap;
return this;
}
public GuiAdapter getAdapter()
{
if (adapter == null)
{
adapter = GraphFactory.getAdapter();
}
return adapter;
}
public Storyboard withAdapter(GuiAdapter adapter)
{
this.adapter = adapter;
return this;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Storyboard withName(String name)
{
setName(name);
return this;
}
public String findRootDir()
{
if (rootDir == null)
{
this.rootDir = "src";
Exception e = new RuntimeException();
StackTraceElement[] stackTrace = e.getStackTrace();
StackTraceElement callEntry = stackTrace[1];
// find first method outside Storyboard
int i = 1;
while (true)
{
callEntry = stackTrace[i];
if (callEntry.getClassName().equals(Storyboard.class.getName())
|| callEntry.getClassName().equals(StoryPage.class.getName()))
{
i++;
continue;
}
else
{
break;
}
}
javaTestFileName = callEntry.getClassName().replaceAll("\\.", "/") + ".java";
// search for a subdirectory containing the javaTestFile of the
// execution directory and search for the subdi
File projectDir = new File(".");
searchDirectoryTree(projectDir);
}
return this.rootDir;
}
public boolean searchDirectoryTree(File projectDir)
{
for (File subDir : projectDir.listFiles())
{
if (subDir.isDirectory())
{
String subPath = subDir.getPath();
if (new File(subPath + "/" + javaTestFileName).exists())
{
// got it
this.rootDir = subDir.getPath().replaceAll("\\\\", "/");
javaTestFileName = "../" + rootDir + "/" + javaTestFileName;
return true;
}
else
{
boolean done = searchDirectoryTree(subDir);
if (done)
return true;
}
}
}
return false;
}
public Storyboard()
{
findRootDir();
Exception e = new RuntimeException();
StackTraceElement[] stackTrace = e.getStackTrace();
StackTraceElement callEntry = stackTrace[1];
String testClassName = null;
if (callEntry.getClassName().equals(StoryPage.class.getName())) {
callEntry = stackTrace[2];
testMethodName = stackTrace[2].getMethodName();
testClassName = CGUtil.shortClassName(stackTrace[2].getClassName());
} else {
testMethodName = stackTrace[1].getMethodName();
testClassName = CGUtil.shortClassName(stackTrace[1].getClassName());
}
String storyName = testMethodName;
if ("main".equals(storyName))
{
storyName = testClassName;
}
if (storyName.startsWith("test"))
{
storyName = storyName.substring(4);
}
setName(storyName);
// this.addToSteps(name);
}
private void addToSteps(String text)
{
StoryboardStep storyStep = new StoryboardStep().withText(text);
this.addToStoryboardSteps(storyStep);
}
// private String rootDir = null;
/**
* @deprecated Storyboards search for their root dir (like src or src/test/java) themself. Thus use the version without parameters.
* @param rootDir The RootDir of Sources
*/
@Deprecated
public Storyboard(String rootDir)
{
if (rootDir == null)
{
// do nothing just for getSendableInstance
return;
}
this.rootDir = rootDir;
Exception e = new RuntimeException();
StackTraceElement[] stackTrace = e.getStackTrace();
StackTraceElement callEntry = stackTrace[1];
javaTestFileName = "../" + rootDir + "/" + callEntry.getClassName().replaceAll("\\.", "/") + ".java";
String methodName = stackTrace[1].getMethodName();
if (methodName.startsWith("test"))
{
methodName = methodName.substring(4);
}
setName(methodName);
this.addToSteps(name);
}
/**
* @deprecated Storyboards search for their root dir (like src or src/test/java) themself.
* Similarly, Storyboards get their name from the method they are used in. Name that method appropriately.
* Use the version without parameters.
*
* @param rootDir The RootDir of Sources
* @param name Name of the html file and page title to be generated.
*/
@Deprecated
public Storyboard(String rootDir, String name)
{
this.rootDir = rootDir;
setName(name);
this.addToSteps(name);
Exception e = new RuntimeException();
StackTraceElement[] stackTrace = e.getStackTrace();
StackTraceElement callEntry = stackTrace[1];
javaTestFileName = "../" + rootDir + "/" + callEntry.getClassName().replaceAll("\\.", "/") + ".java";
}
private void writeToFile(String imgName, String fileText)
{
try
{
if (imgName.startsWith("<"))
{
System.out.println("Ups, invalid file name: " + imgName);
return;
}
File oldFile = new File("doc/" + imgName);
if (oldFile.exists())
{
// load old file content and compare with new fileText, if no
// change, do not write
BufferedReader in = new BufferedReader(new FileReader(oldFile));
StringBuilder oldFileText = new StringBuilder();
String line = in.readLine();
while (line != null)
{
oldFileText.append(line).append("\n");
line = in.readLine();
}
in.close();
String oldFileString = oldFileText.toString().trim();
fileText = fileText.replaceAll("\\r", "");
int oldStringLength = oldFileString.length();
int newStringLength = fileText.length();
if (oldFileString.equals(fileText.trim()))
{
// do not write file, no change
return;
}
}
BufferedWriter out = new BufferedWriter(new FileWriter("doc/" + imgName));
out.write(fileText);
out.close();
}
catch (IOException e)
{
// e.printStackTrace();
}
}
// private int stepCounter = 0;
public Storyboard addStep(String txt)
{
if (stepCounter == 0)
{
this.add("Start: " + txt);
this.setStepCounter(this.getStepCounter() + 1);
}
else
{
StringBuilder buf = new StringBuilder("");
CGUtil.replaceAll(buf,
"stepCounter", "" + stepCounter,
"text", txt);
this.add(buf.toString());
this.setStepCounter(this.getStepCounter() + 1);
}
return this;
}
public void add(String string)
{
synchronized (this)
{
this.addToSteps(string);
this.notifyAll();
}
}
public void addText(String string)
{
this.add(string);
}
public Storyboard withMap(IdMap map)
{
this.jsonIdMap = map;
return this;
}
public void coverage4GeneratedModelCode(Object root)
{
if (root == null)
{
return;
}
// derive creator class from root and create idMap
String className = root.getClass().getName();
String packageName = CGUtil.packageName(className) + ".util";
className = packageName + ".CreatorCreator";
Object idMap = null;
try
{
if (jsonIdMap == null)
{
Class> creatorClass = Class.forName(className);
Method method = creatorClass.getDeclaredMethod("createIdMap", String.class);
method.setAccessible(true);
idMap = method.invoke(null, "debug");
jsonIdMap = (IdMap) idMap;
}
if (largestJsonArray == null)
{
largestJsonArray = jsonIdMap.toJsonArray(root);
}
IdMap copyMap = (IdMap) new IdMap().with(jsonIdMap);
copyMap.decode(largestJsonArray);
coverSetAndPOClasses(copyMap);
coverSeldomModelMethods(copyMap);
}
catch (Exception e)
{
// cannot find creator creator class, sorry.
// e.printStackTrace();
}
}
public void coverSeldomModelMethods(IdMap copyMap) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException
{
LinkedHashSet handledClassesNames = new LinkedHashSet();
LinkedHashSet keySet = new LinkedHashSet();
keySet.addAll(copyMap.getCreators().keySet());
// loop through objects
for (String key : keySet)
{
try
{
Object object = keySet;
// that class is already handled?
String className = object.getClass().getName();
if (handledClassesNames.contains(className))
{
continue;
}
handledClassesNames.add(className);
// call toString
object.toString();
Class extends Object> objectClass = object.getClass();
try
{
Method addPropertyChangeListenerMetod = objectClass.getMethod("addPropertyChangeListener",
PropertyChangeListener.class);
addPropertyChangeListenerMetod.invoke(object, new Object[]
{ null });
}
catch (Exception e)
{
// dont worry
}
// call createXY methods (some of them are not used in practice, e.g
// student.createUniversity())
for (Method m : objectClass.getMethods())
{
String methodName = m.getName();
if (methodName.startsWith("create") && m.getParameterTypes().length == 0)
{
try
{
m.invoke(object);
}
catch (Exception e)
{
}
}
if (methodName.startsWith("get") && methodName.endsWith("Transitive"))
{
try
{
m.invoke(object);
}
catch (Exception e)
{
}
}
}
Method removeMethod = objectClass.getMethod("removeYou");
removeMethod.invoke(object);
}
catch (Exception x)
{
// dont worry
}
}
}
public void coverSetAndPOClasses(IdMap copyMap) throws NoSuchMethodException, SecurityException
{
// loop through objects in idMap, pack them into set, read and write
// all attributes
LinkedHashSet keySet = new LinkedHashSet();
keySet.addAll(copyMap.getKeyValue().keySet());
for (String key : keySet)
{
Object object = copyMap.getObject(key);
SendableEntityCreator creatorClass = copyMap.getCreatorClass(object);
String className = object.getClass().getName();
String packageName = CGUtil.packageName(className) + ".util";
String setClassName = packageName + "." + CGUtil.shortClassName(className) + "Set";
try
{
Class> setClass = Class.forName(setClassName);
Object setObject = setClass.newInstance();
if (setObject instanceof ModelSet)
{
// cover ModelSet methods
String entryType = ((ModelSet) setObject).getEntryType();
}
// add entry
Method withMethod = setClass.getMethod("with", new Class[]
{ Object.class });
withMethod.invoke(setObject, object);
withMethod.invoke(setObject, setObject);
PatternObject patternObject = null;
Class patternObjectClass = null;
Method hasPOMethod = null;
try
{
hasPOMethod = setClass.getMethod("has" + CGUtil.shortClassName(className) + "PO");
patternObject = (PatternObject) hasPOMethod.invoke(setObject);
patternObjectClass = patternObject.getClass();
// call allMatches
Method allMatchesMethod = patternObjectClass.getMethod("allMatches");
allMatchesMethod.invoke(patternObject);
// Method poConstructor =
// patternObjectClass.getMethod(CGUtil.shortClassName(patternObjectClass.getName()));
// poConstructor.invoke(null);
}
catch (Exception e)
{
// e.printStackTrace();
}
try
{
hasPOMethod = setClass.getMethod("filter" + CGUtil.shortClassName(className) + "PO");
patternObject = (PatternObject) hasPOMethod.invoke(setObject);
patternObjectClass = patternObject.getClass();
// call allMatches
Method allMatchesMethod = patternObjectClass.getMethod("allMatches");
allMatchesMethod.invoke(patternObject);
// Method poConstructor =
// patternObjectClass.getMethod(CGUtil.shortClassName(patternObjectClass.getName()));
// poConstructor.invoke(null);
}
catch (Exception e)
{
// e.printStackTrace();
}
// toString
String text = setObject.toString();
// loop through attributes
for (String attrName : creatorClass.getProperties())
{
try
{
// call getter
Method getMethod = setClass.getMethod("get" + StrUtil.upFirstChar(attrName));
Object value = getMethod.invoke(setObject);
Object setValue = null;
// get direct value
if (value instanceof Collection)
{
setValue = value;
value = ((Collection>) value).iterator().next();
}
Class> valueClass = value.getClass();
if (value instanceof Integer)
{
valueClass = int.class;
}
else if (value instanceof Double)
{
valueClass = double.class;
}
else if (value instanceof Long)
{
valueClass = long.class;
}
else if (value instanceof Float)
{
valueClass = float.class;
}
else if (value instanceof Boolean)
{
valueClass = boolean.class;
}
// call setter
Method setMethod = setClass.getMethod("with" + StrUtil.upFirstChar(attrName), valueClass);
setMethod.invoke(setObject, value);
try
{
Method unsetMethod = setClass.getMethod("without" + StrUtil.upFirstChar(attrName), valueClass);
unsetMethod.invoke(setObject, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = setClass.getMethod("has" + StrUtil.upFirstChar(attrName), valueClass);
hasMethod.invoke(setObject, value);
hasMethod = setClass.getMethod("has" + StrUtil.upFirstChar(attrName), valueClass, valueClass);
hasMethod.invoke(setObject, value, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = setClass.getMethod("has" + StrUtil.upFirstChar(attrName), Object.class);
hasMethod.invoke(setObject, value);
if (setValue != null)
{
hasMethod.invoke(setObject, setValue);
}
}
catch (Exception e)
{
}
try
{
Method hasMethod = setClass.getMethod("filter" + StrUtil.upFirstChar(attrName), valueClass);
hasMethod.invoke(setObject, value);
hasMethod = setClass.getMethod("filter" + StrUtil.upFirstChar(attrName), valueClass, valueClass);
hasMethod.invoke(setObject, value, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = setClass.getMethod("filter" + StrUtil.upFirstChar(attrName), Object.class);
hasMethod.invoke(setObject, value);
if (setValue != null)
{
hasMethod.invoke(setObject, setValue);
}
}
catch (Exception e)
{
}
// also cover creatorclass set method
creatorClass.setValue(object, attrName, value, "");
creatorClass.setValue(object, attrName, value, IdMap.REMOVE);
patternObject = (PatternObject) hasPOMethod.invoke(setObject);
if (patternObject != null)
{
// cover attr methods for pattern object
try
{
// getName
getMethod = patternObjectClass.getMethod("get" + StrUtil.upFirstChar(attrName));
getMethod.invoke(patternObject);
}
catch (Exception e)
{
}
try
{
// createName
withMethod = patternObjectClass.getMethod("with" + StrUtil.upFirstChar(attrName), valueClass);
withMethod.invoke(patternObject, value);
}
catch (Exception e)
{
}
try
{
// createName
Method createMethod = patternObjectClass.getMethod("create" + StrUtil.upFirstChar(attrName),
valueClass);
createMethod.invoke(patternObject, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = patternObjectClass.getMethod("has" + StrUtil.upFirstChar(attrName),
valueClass);
hasMethod.invoke(patternObject, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = patternObjectClass.getMethod("filter" + StrUtil.upFirstChar(attrName),
valueClass);
hasMethod.invoke(patternObject, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = patternObjectClass.getMethod("has" + StrUtil.upFirstChar(attrName),
valueClass, valueClass);
hasMethod.invoke(patternObject, value, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = patternObjectClass.getMethod("filter" + StrUtil.upFirstChar(attrName),
valueClass, valueClass);
hasMethod.invoke(patternObject, value, value);
}
catch (Exception e)
{
}
try
{
Method hasMethod = patternObjectClass.getMethod("has" + StrUtil.upFirstChar(attrName));
hasMethod.invoke(patternObject);
}
catch (Exception e)
{
}
try
{
Method hasMethod = patternObjectClass.getMethod("filter" + StrUtil.upFirstChar(attrName));
hasMethod.invoke(patternObject);
}
catch (Exception e)
{
}
try
{
Method method = patternObjectClass.getMethod("create" + StrUtil.upFirstChar(attrName));
method.invoke(patternObject);
}
catch (Exception e)
{
}
try
{
String poClassName = CGUtil.helperClassName(valueClass.getName(), "PO");
SendableEntityCreator poCreator = copyMap.getCreator(poClassName, true);
Object po = poCreator.getSendableInstance(false);
try
{
Method method = patternObjectClass.getMethod("has" + StrUtil.upFirstChar(attrName),
po.getClass());
method.invoke(patternObject, po);
}
catch (Exception e)
{
}
try
{
Method method = patternObjectClass.getMethod("filter" + StrUtil.upFirstChar(attrName),
po.getClass());
method.invoke(patternObject, po);
}
catch (Exception e)
{
}
try
{
Method method = patternObjectClass.getMethod("with" + StrUtil.upFirstChar(attrName),
po.getClass());
method.invoke(patternObject, po);
}
catch (Exception e)
{
}
try
{
Method method = patternObjectClass.getMethod("without" + StrUtil.upFirstChar(attrName),
po.getClass());
method.invoke(patternObject, po);
}
catch (Exception e)
{
}
try
{
Method method = patternObjectClass.getMethod("create" + StrUtil.upFirstChar(attrName),
po.getClass());
method.invoke(patternObject, po);
}
catch (Exception e)
{
}
}
catch (Exception e)
{
}
}
}
catch (Exception e)
{
// no problem, go on with next attr
}
}
// del entry
Method withoutMethod = setClass.getMethod("without", object.getClass());
withoutMethod.invoke(setObject, object);
creatorClass.getValue(object, "foo.bar");
// creatorClass.removeObject(object);
Method removeMethod = creatorClass.getClass().getMethod("removeObject", Object.class);
removeMethod.invoke(creatorClass, object);
}
catch (Exception e)
{
// no problem, just lower coverage
// e.printStackTrace();
}
}
// go through all creator classes and call createIdMap
for ( SendableEntityCreator creator : copyMap.getCreators().values())
{
try
{
Method createIdMapMethod = creator.getClass().getMethod("createIdMap", String.class);
createIdMapMethod.invoke(creator, "t");
}
catch (Exception e)
{
}
}
}
/**
* Add a class diagram to the generated html page.
* @param model The ClassModel for drawing
*/
public void addClassDiagram(ClassModel model)
{
String diagName = this.getName() + "ClassDiagram" + this.getStoryboardSteps().size();
diagName = model.dumpClassDiagram(diagName);
this.add(diagName);
}
public void addObjectDiagramWith(Object... elems)
{
ArrayList