I am new to JUnit testing. I am trying to test a method that exports a report. Basically, this method pops up a save menu to select where to save the file at and also gets the report from another class. I am not sure what I need to test here or how to even test it. I have added my JMenuItem and my actionEvent as well. Any ideas or help would be greatly accepted.
Here is my JMenuItem:
JMenuItem jMenuFileexportProjectReport = new JMenuItem(exportProjectReportAction);
Here is my Action event for the JMenuItem:
public Action exportProjectReportAction =
new AbstractAction(Local.getString("Export Project Report")) {
public void actionPerformed(ActionEvent e) {
reportExportAction(e);
}
};
Here is my method to export the report:
public void reportExportAction(ActionEvent e) {
JFileChooser chooser = new JFileChooser();
chooser.setFileHidingEnabled(false);
chooser.setDialogTitle(Local.getString("Export Project Report"));
chooser.setAcceptAllFileFilterUsed(false);
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
chooser.addChoosableFileFilter(
new AllFilesFilter(AllFilesFilter.XHTML));
chooser.addChoosableFileFilter(new AllFilesFilter(AllFilesFilter.HTML));
String lastSel = (String) Context.get("LAST_SELECTED_EXPORT_FILE");
if (lastSel != null) {
chooser.setCurrentDirectory(new File(lastSel));
}
ProjectExportDialog dlg =
new ProjectExportDialog(
App.getFrame(),
Local.getString("Export Project Report"),
chooser);
String enc = (String) Context.get("EXPORT_FILE_ENCODING");
if (enc != null) {
dlg.encCB.setSelectedItem(enc);
}
Dimension dlgSize = new Dimension(550, 500);
dlg.setSize(dlgSize);
Dimension frmSize = App.getFrame().getSize();
Point loc = App.getFrame().getLocation();
dlg.setLocation(
(frmSize.width - dlgSize.width) / 2 + loc.x,
(frmSize.height - dlgSize.height) / 2 + loc.y);
dlg.setVisible(true);
if (dlg.CANCELLED) {
return;
}
Context.put(
"LAST_SELECTED_EXPORT_FILE",
chooser.getSelectedFile().getPath());
int ei = dlg.encCB.getSelectedIndex();
enc = null;
if (ei == 1) {
enc = "UTF-8";
}
boolean nument = (ei == 2);
File f = chooser.getSelectedFile();
boolean xhtml =
chooser.getFileFilter().getDescription().indexOf("XHTML") > -1;
CurrentProject.save();
ReportExporter.export(CurrentProject.get(), chooser.getSelectedFile(), enc, xhtml,
nument);
}
Class the creates a HTML report:
public class ReportExporter {
static boolean _chunked = false;
static boolean _num = false;
static boolean _xhtml = false;
static boolean _copyImages = false;
static File output = null;
static String _charset = null;
static boolean _titlesAsHeaders = false;
static boolean _navigation = false;
static String charsetString = "\n";
public static void export(Project prj, File f, String charset, boolean xhtml, boolean chunked) {
_chunked = chunked;
_charset = charset;
_xhtml = xhtml;
if (f.isDirectory()) {
output = new File(f.getPath() + "/Project Report.html");
}
else {
output = f;
}
NoteList nl = CurrentStorage.get().openNoteList(prj);
Vector notes = (Vector) nl.getAllNotes();
//Creates Labels for the HTML output for each section.
String notesLabelHTML = "Notes";
String tasksLabelHTML = "Tasks";
String eventsLabHTML = "Events";
//NotesVectorSorter.sort(notes);
Collections.sort(notes);
Writer fw;
if (output.getName().indexOf(".htm") == -1) {
String dir = output.getPath();
String ext = ".html";
String nfile = dir + ext;
output = new File(nfile);
}
try {
if (charset != null) {
fw = new OutputStreamWriter(new FileOutputStream(output),
charset);
charsetString = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset="
+ charset + "\" />";
}
else
fw = new FileWriter(output);
}
catch (Exception ex) {
new ExceptionDialog(ex, "Failed to write to " + output, "");
return;
}
//Writes the title and the notes section of the HTMl Report
write(fw, "<html>\n<head>\n" + charsetString + "<title>"
+ prj.getTitle()
+ "</title>\n</head>\n<body>\n<h1 class=\"projecttitle\">"
+ prj.getTitle() + "</h1>\n" +"\n<br>\n"
+ "</title>\n</head>\n<body>\n<h2 class=\"projecttitle\">"
+ notesLabelHTML + "</h2>\n" );
generateChunks(fw, notes);
//Writes the Task section of the HTML Report
write(fw, "\n<hr></hr><a" +"</title>\n</head>\n<body>\n<h2 class=\"projecttitle\">" + "\n<br>\n"
+ tasksLabelHTML + "</h2>\n" );
//writes the Events section of the HTML Report
write(fw, "\n<hr></hr><a" +"</title>\n</head>\n<body>\n<h2 class=\"projecttitle\">" + "\n<br>\n"
+ eventsLabHTML + "</h2>\n" );
//Writes the ending of the report with the data and time
write(fw, "\n<hr></hr><a "
+ "\n<br></br>\n" + new Date().toString()
+ "\n</body>\n</html>");
try {
fw.flush();
fw.close();
}
catch (Exception ex) {
new ExceptionDialog(ex, "Failed to write to " + output, "");
}
}
public static String getNoteHTML(Note note) {
String text = "";
StringWriter sw = new StringWriter();
AltHTMLWriter writer = new AltHTMLWriter(sw,
(HTMLDocument) CurrentStorage.get().openNote(note), _charset,
_num);
try {
writer.write();
sw.flush();
sw.close();
}
catch (Exception ex) {
new ExceptionDialog(ex);
}
text = sw.toString();
if (_xhtml) {
text = HTMLFileExport.convertToXHTML(text);
}
text = Pattern
.compile("<body(.*?)>", java.util.regex.Pattern.DOTALL
+ java.util.regex.Pattern.CASE_INSENSITIVE).split(text)[1];
text = Pattern
.compile("</body>", java.util.regex.Pattern.DOTALL
+ java.util.regex.Pattern.CASE_INSENSITIVE).split(text)[0];
text = "<div class=\"note\">" + text + "</div>";
if (_titlesAsHeaders) {
text = "\n\n<div class=\"date\">"
+ note.getDate().getFullDateString()
+ ":</div>\n<h1 class=\"title\">" + note.getTitle()
+ "</h1>\n" + text;
}
return text;
}
private static String generateNav(Note prev, Note next) {
String s = "<hr></hr><div class=\"navigation\"><table border=\"0\" width=\"100%\" cellpadding=\"2\"><tr><td width=\"33%\">";
if (prev != null) {
s += "<div class=\"navitem\"><a href=\"" + prev.getId() + ".html\">"
+ Local.getString("Previous") + "</a><br></br>"
+ prev.getDate().getMediumDateString() + " "
+ prev.getTitle() + "</div>";
}
else {
s += " ";
s += "</td><td width=\"34%\" align=\"center\"><a href=\""
+ output.getName()
+ "\">Up</a></td><td width=\"33%\" align=\"right\">";
}
if (next != null) {
s += "<div class=\"navitem\"><a href=\"" + next.getId() + ".html\">"
+ Local.getString("Next") + "</a><br></br>"
+ next.getDate().getMediumDateString() + " "
+ next.getTitle() + "</div>";
}
else {
s += " ";
}
s += "</td></tr></table></div>\n";
return s;
}
private static void generateChunks(Writer w, Vector notes) {
Object[] n = notes.toArray();
for (int i = 0; i < n.length; i++) {
Note note = (Note) n[i];
CalendarDate d = note.getDate();
if (_chunked) {
File f = new File(output.getParentFile().getPath() + "/"
+ note.getId()
+ ".html");
Writer fw = null;
try {
if (_charset != null) {
fw = new OutputStreamWriter(new FileOutputStream(f),
_charset);
}
else {
fw = new FileWriter(f);
}
String s = "<html>\n<head>\n"+charsetString+"<title>" + note.getTitle()
+ "</title>\n</head>\n<body>\n" + getNoteHTML(note);
if (_navigation) {
Note nprev = null;
if (i > 0) {
nprev = (Note) n[i - 1];
}
Note nnext = null;
if (i < n.length - 1) {
nnext = (Note) n[i + 1];
}
s += generateNav(nprev, nnext);
}
s += "\n</body>\n</html>";
fw.write(s);
fw.flush();
fw.close();
}
catch (Exception ex) {
new ExceptionDialog(ex, "Failed to write to " + output, "");
}
}
else {
write(w, "<a name=\"" + "\">" + note.getDate() +"</a>\n" + getNoteHTML(note) + "</a>\n");
}
}
}
private static void write(Writer w, String s) {
try {
w.write(s);
}
catch (Exception ex) {
new ExceptionDialog(ex, "Failed to write to " + output, "");
}
}
As it has already been mentioned, basically you should move out the application logic from the GUI code.
Unit testing usually doesn't work like write working code then add tests to it. First you should think about
what is the testable part of the logic and what isn't
how will you separate them
how will you test the testable part
Directly testing GUI code usually does not work (except with some rare and well-designed UI frameworks), since your test would have to deal with a lot of technical problems (like framework initialization, instantiation of UI objects, triggering events properly, etc). So you should create a more abstract layer of the app. In a bigger picture, it will lead to the well-known Model-View-Controller pattern (de-facto standard design pattern for user interfaces since Smalltalk), where the model layer is independent from the UI framework, therefore it is simply testable.
So as some kind of case study, lets walk through the above concepts:
First lets check what is testable, what isn't and what stops you from writing tests:
chooser.setDialogTitle(Local.getString("Export Project Report"));
This single line deals with both rendering and i18n, not really a good idea. Furthermore, the use of static methods is a top blocker antipattern for writing tests.
String lastSel = (String) Context.get("LAST_SELECTED_EXPORT_FILE");
Static method call here again, also it would be hard to determine the responsibility of that Context class.
Local.getString("Export Project Report")
also a static call, kind of duplicate code too
...and so on (static calls everywhere). Now lets see what kind of more abstract model we can create for this. First lets start with
some textual description of the requirements:
There is a title (used both in the JFileChooser and the ProjectExportDialog) to be internationalized by key "Export Project Report"
there is a previously selected directory (lastSel) which value we take granted at the beginning
the encoding (enc) is similar to enc, nullable value too
the dialog location (positioning) contains some arithmetic, we should test it
if the user selects a file, then we should store it as the last selected directory
there is an opened (current) project too, which we will save at the end
there is something you call "nument", I don't understand what it is, but it should be true if the user selects the 2nd entry from dlg.encCB
Untestable parts:
chooser... calls: UI-specific configuration of the JFileChooser, also it doesn't contain any control structures or calculation, so we won't test it
Now we are going to design a testable model class. While doing it, we will keep two principles in mind:
we are going to put as much logic into the model as we can
we don't want to rewrite your entire app for this time. So instead of getting rid of all the static calls, we abstract them away as simply as possible.
So now lets create some sort of abstract model for this (summary after code):
public class ProjectExportModel {
// see the reasoning below
public static ProjectExportModel create() {
return new ProjectExportModel(Local::getString,
(String) Context.get("LAST_SELECTED_EXPORT_FILE"),
Context::put);
}
private final Function<String, String> i18n;
private final File lastSelectedExportFile;
private final Consumer<File> lastSelectedFileSaver;
private String encoding;
private boolean nument;
private boolean xhtml;
public ProjectExportModel(final Function<String, String> i18n, final File lastSelectedExportFile,
final Consumer<File> lastSelectedFileSaver) {
this.i18n = i18n;
this.lastSelectedExportFile = lastSelectedExportFile;
this.lastSelectedFileSaver = lastSelectedFileSaver;
}
/**
* Called after a file has been selected from the JFileChooser
*
* Things to test:
* - lastSelectedFileSaver.accept(file.getPath()) should be called - you may use a
* mocking library to test
* - the xhtml flag should be changed - testing is easy
*
*/
public void fileSelected(final File file) {
// TODO
}
/**
* At this point we break a bit the concept of the UI-independent model layer, since Point and Dimension
* are UI-framework-related classes. But these 2 classes are easy to instantiate and easy to assert on the
* returned value, so good-enough solution this time.
*/
public Point getDialogLocation(final Dimension frameSize, final Point frameLocation) {
return null; // TODO implement the positioning
}
public String getFrameTitle() {
// TODO test if it calls and returns i18n.get("Export Project Model") - you need mocking here too
return null;
}
/**
* Two things to be tested here:
* - if CurrentProject.save() is called
* - if ReportExporter.export(...) is called with the right parameters
*
* You are quite stuck here, since static methods cannot be mocked. Instead it would be better to change your APIs to make
* these instance methods, since in the current way it is untestable. After changing these to instance methods, you should add
* 2 more parameters to the constructor: a Project instance and a ReportExporter instance.
* You can use mockito or easymock for mocking.
*/
public void save() {
}
/**
* You may call it from the view layer after calling fileSelected().
*
* To be tested:
* - the proper change of the encoding member
* - the proper change of the nument member
*/
public void selectedEncodingChanged(final int selectedIndex) {
// TODO implemenent the change of encoding and nument member
}
}
Summary:
this class is easy to instantiate and test
in the tests you will use its explicit constructor to create instances
for "production" usage, you will have to create a View class, which handles the swing-related code, accepts a ProjectExportModel instance as its parameter, and calls it methods, so you wire the tested model into the untestable UI-related code, while keeping the latter one minimal. Also, in this case you will create the model instance with ProjectExportModel.create() , since that methods wires the further dependencies in a way that it will more or less nicely interact with the other static methods of your app. This is a good technique for extracting testable parts while you don't necessarily have to remove all static methods from the app, we have just separated them away.
Related
I have been asked to validate the UI changes in the product(on all pages). If there are any new changes/features introduced or if there are any UI issues to be captured in automation ( Selenium, JAVA). What would be the best approach to achieve this result?
I had a similar task, and my solution was the following:
I created a folder where I saved the body html of the page into text files.
Then the logic was to run a test where it creates current text files with the body html. Finally comparing these 2 files and seeing if there is any difference.
I have used the RestAssured API library to achieve this goal. So it is a mix of using API responses logic and comparing 2 files. You might have to replace some dynamic Strings in between.
The code looks similar to this:
#Test --> here you can use a data provider
public void verifyHTMLConsistency(String URL) throws Exception {
Path pathCurrent;
Path pathBaseline;
URL = "url-of-the-page";
// get the bodyHTML text, by giving the URL input.
String bodyHTMLText = given()
.when()
.get(URL)
.then()
.log()
.body().extract().asString();
bodyHTMLText = returnAllResultsBetweenValues(bodyHTMLText);
// 2
pathCurrent = Paths.get(System.getProperty("user.dir") + "/reportFolder/CurrentTestTextFiles/" + reportName + ".txt");
writeFile(pathCurrent, bodyHTMLText);
pathBaseline = Paths.get(System.getProperty("user.dir") + "/reportFolder/BaseLineTextFiles/" + reportName + ".txt");
// 3 open the text files in BufferedReader
BufferedReader readCurrentFile = new BufferedReader(new FileReader(String.valueOf(pathCurrent)));
BufferedReader readerBaselineFile = new BufferedReader(new FileReader(String.valueOf(pathBaseline)));
assertTrue(sameContentWithFileReader(readCurrentFile, readerBaselineFile,reportName));
}
static void writeFile(Path path, String content) throws IOException {
Files.write(path, content.getBytes(StandardCharsets.UTF_8));
}
private static boolean sameContentWithFileReader(BufferedReader reader1, BufferedReader reader2, String reportName) throws IOException {
String line1 = reader1.readLine();
String line2 = reader2.readLine();
boolean flag = true;
int lineNum = 1;
while (line1 != null || line2 != null)
{
if(line1 == null || line2 == null)
{
flag = false;
break;
}
else if(! line1.equalsIgnoreCase(line2))
{
flag = false;
break;
}
line1 = reader1.readLine();
line2 = reader2.readLine();
lineNum++;
}
if(flag)
{
System.out.println("Two files have same content.");
}
else
{
System.out.println("Two files have different content. They differ at line "+lineNum);
System.out.println("-------------------");
System.out.println("CURRENTFILE has "+line1+" || at line "+lineNum);
System.out.println("-------------------");
System.out.println("BASELINE has "+line2+" || at line "+lineNum);
}
reader1.close();
reader2.close();
return flag;
}
// to replace some dynamic values
private static String returnAllResultsBetweenValues(String bodyHTMLText){
String[] stringToBeReplacedBottomMultiple;
try{
stringToBeReplacedBottomMultiple =
StringUtils.substringsBetween(bodyHTMLText,
"Text1",
"Text2");
// replace this string with a test text
for(String results : stringToBeReplacedBottomMultiple){
bodyHTMLText = bodyHTMLText.replace(results, "---QA---");
}
}catch(Exception e){
System.out.println(e);
}
return bodyHTMLText;
}
Hope this helps.
I am a beginner programmer so please excuse any technically incorrect statements/incorrect use of terminology.
I am trying to make a program that reduces CNF SAT in DIMACS format to 3SAT, and then from 3SAT to 3Graph Coloring, and then 3Graph coloring to SAT again. The idea is to make it circular so that the output from one reduction can be piped straight into the input of another, AKA if you reduce a CNF to 3SAT, the program should automatically reduce the 3SAT to Graph coloring after if the use specifies it to.
I have chosen to represent CNFs in a LinkedHashMap in a class called CNFHandler. The LinkedHashMap is where File = the DIMACS cnf formatted file and the CNF object is the CNF (which contains an ArrayList of Clause objects) that corresponds to the CNF.
In my CNFHandler class, I have a reduce object, and it's in this object that I am trying to initiate my piping functionality:
package CNFHandler;
import SAT_to_3SAT_Reducer.Reducer;
import java.io.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
public class CNFHandler {
private Map<File, CNF> allCNFs = new LinkedHashMap<>();
private CNFReader reader;
private Reducer reducer = new Reducer();
// PIPES
private Optional<ObjectInputStream> inputPipe;
private Optional<ObjectOutputStream> outputPipe;
// Instantiate Pipes
public void setInputPipe(ObjectInputStream inputStream) {
this.inputPipe = Optional.of(inputStream);
}
public void setOutputPipe(ObjectOutputStream outputStream) {
this.outputPipe = Optional.of(outputStream);
}
//...
// Skipping lines for brevity
//...
public void reduce(String filePath) {
File path = new File(filePath);
addCNF(filePath);
CNF result = reducer.reduce(allCNFs.get(path));
if (!outputPipe.isPresent()) {
System.out.println(result.toDIMACS());
} else {
try {
outputPipe.get().writeObject(result);
outputPipe.get().close();
} catch (Exception e){
e.printStackTrace();
}
}
}
}
When I try to run "writeObject" (within the try block in the reduce() method) the program doesn't seem to go past that point. I've tried using breakpoints in IntelliJ to see what's going on, but the best I could figure out was as follows:
A Native method called waitForReferencePendingList() seems to be stuck waiting for something, and that's why it won't go past the writeObject method
IntelliJ tells me "Connected to the target VM, address: '127.0.0.1:51236', transport: 'socket'" but I'm not sure why because I'm not using Sockets anywhere in my program
Here is the code for my Main method where I instantiate the ObjectOutputStreams :
import CNFHandler.CNFHandler;
import GraphHandler.GraphHandler;
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
String inFile = "short_cnf.cnf";
PipedOutputStream _S_3S_OUT_PIPE_STREAM = new PipedOutputStream();
PipedInputStream _S_3S_IN_PIPE_STREAM = new PipedInputStream();
_S_3S_IN_PIPE_STREAM.connect(_S_3S_OUT_PIPE_STREAM);
ObjectOutputStream _S_3S_OUT_OBJECT_STREAM = new ObjectOutputStream(_S_3S_OUT_PIPE_STREAM);
ObjectInputStream _S_3S_IN_OBJEECT_STREAM = new ObjectInputStream(_S_3S_IN_PIPE_STREAM);
CNFHandler handler = new CNFHandler();
handler.setOutputPipe(_S_3S_OUT_OBJECT_STREAM);
handler.reduce(inFile);
PipedOutputStream _3S_G_OUT = new PipedOutputStream();
PipedInputStream _3S_G_IN = new PipedInputStream();
_3S_G_IN.connect(_3S_G_OUT);
ObjectOutputStream _3S_G_OUT_STREAM = new ObjectOutputStream(_3S_G_OUT);
ObjectInputStream _3S_G_IN_STREAM = new ObjectInputStream(_3S_G_IN);
GraphHandler graphHandler = new GraphHandler();
graphHandler.setInputPipe(_S_3S_IN_OBJEECT_STREAM);
graphHandler.setOutputPipe(_3S_G_OUT_STREAM);
graphHandler.reduce();
} catch (IOException e) {
e.printStackTrace();
}
}
}
The other weird thing is that the writeObject() method seems to work if I use a different kind of object, for example, if I instantiate a String within the writeObject() method in the same place it's being called in reduce(), or if I instantiate a new CNF object in the same place, it WILL write the object. But I can't do it this way because I have to pass along the values of the object as well (the clauses, etc.) so I don't know what to do.
This is my CNF class, in brief:
package CNFHandler;
import java.io.Serializable;
import java.util.*;
public class CNF implements Serializable {
protected int numVars;
protected int numClauses;
protected String fileName;
// store all variables with no duplicates
protected Set<String> allLiterals = new HashSet<>();
protected ArrayList<Clause> clauses = new ArrayList<>();
/*
for printing to DIMACS: keep track of the max # of
literals that are needed to print a clause
for example if all clauses in the CNF file contain
2 literals, and only one contains 3 literals
then the literalsize will be 3 to ensure things
are printed with proper spacing
*/
protected int literalSize = -20;
/*
keep track of the label referring to the highest #ed literal
just in case they are not stored in order -- this way when we perform
reductions we can just add literals to the end and be sure we are not
duplicating any
*/
protected int highestLiteral = -10;
public CNF(String fileName) {
this.fileName = fileName;
}
protected void addClause(String[] inputs) {
try {
Clause clauseToAdd = new Clause();
// add literals to the hashset, excluding dashes that indicate negative literals
for (int i = 0; i < inputs.length - 1; i++) {
// removing whitespace from the input
String toAdd = inputs[i].replaceAll("\\s+", "");;
// in case the variable is false (has a dash before the int), remove the dash to standardize storage
String moddedToAdd = inputs[i].replaceAll("[-]*", "");
/*
if an unknown variable is in the stream, reject it.
(we're basically checking here if the variable set is full,
and if it is and the variable we're trying to add is new,
then it can't be added)
*/
if ((!allLiterals.contains(moddedToAdd)) && (allLiterals.size() == numVars) && (moddedToAdd.trim().length() > 0)) {
throw new FailedCNFException();
}
// add the original input (so not the regex'd one but the one that would be false if it had been input as false
clauseToAdd.addLiteral(toAdd);
if (!allLiterals.contains(moddedToAdd) && !moddedToAdd.equalsIgnoreCase("")) {
allLiterals.add(moddedToAdd);
/*
change the highestLiteral value if the literal being added is "bigger" than the others that have been seen
*/
if(highestLiteral < Integer.parseInt(moddedToAdd)) {
highestLiteral = Integer.parseInt(moddedToAdd);
}
}
}
if (clauseToAdd.getNumberOfLiterals() > literalSize) {
literalSize = clauseToAdd.getNumberOfLiterals();
}
clauses.add(clauseToAdd);
} catch (FailedCNFException e) {
System.out.println("The number of variables that have been introduced is too many!");
}
}
public void makeClause(String[] inputs) {
try {
if (inputs[inputs.length - 1].equals("0")) {
addClause(inputs);
} else throw new FailedCNFException();
} catch (FailedCNFException f) {
System.out.println("There is no 0 at the end of this line: ");
for (String s : inputs ) {
System.out.print(s + " ");
}
System.out.println();
}
}
public void initializeClauses (String[] inputs) {
setNumVars(inputs[2]);
setNumClauses(inputs[3]);
}
public String toDIMACS () {
String toReturn = "p cnf " + getNumVars() + " " + getNumClauses() + "\n";
for(int i = 0; i < clauses.size()-1; i++){
Clause c = clauses.get(i);
toReturn += c.toDIMACS(literalSize) + "\n";
}
toReturn += clauses.get(clauses.size()-1).toDIMACS(literalSize);
return toReturn;
}
/*
Override tostring method to print clauses in human-readable format
*/
#Override
public String toString () {
if(highestLiteral != -10) {
String toReturn = "(";
for (int i = 0; i < clauses.size() - 1; i++) {
Clause c = clauses.get(i);
toReturn += c + "&&";
}
toReturn += clauses.get(clauses.size() - 1).toString() + ")";
return toReturn;
} else {
return "Add some clauses!";
}
}
public String toString (boolean addFile) {
String toReturn = "";
if (addFile) {
toReturn += "src/test/ExampleCNFs/" + fileName + ".cnf: \n";
}
toReturn += "(";
for(int i = 0; i < clauses.size()-1; i++){
Clause c = clauses.get(i);
toReturn += c + "&&";
}
toReturn += clauses.get(clauses.size()-1).toString() + ")";
return toReturn;
}
//=============================================================================
// HELPER FUNCTIONS
//=============================================================================
public void setNumVars(String vars) {
numVars = Integer.parseInt(vars);
}
public void setNumClauses(String clauses) {
numClauses = Integer.parseInt(clauses);
}
public Clause getClause(int index) {
return clauses.get(index);
}
public void addLiteral(int newLiteral) {
allLiterals.add(String.valueOf(newLiteral));
}
public void addLiterals(Set<String> newLiterals) {
allLiterals.addAll(newLiterals);
}
public void addClauses(ArrayList<Clause> toAdd, int maxLiterals) {
clauses.addAll(toAdd);
numClauses += toAdd.size();
// update literalsize if need be
if (maxLiterals > literalSize) {
literalSize = maxLiterals;
}
}
//=============================================================================
// GETTERS AND SETTERS
//=============================================================================
public void setNumVars(int numVars) {
this.numVars = numVars;
}
public void setNumClauses(int numClauses) {
this.numClauses = numClauses;
}
public int getNumVars() {
return numVars;
}
public int getNumClauses() {
return numClauses;
}
public ArrayList<Clause> getClauses() {
return clauses;
}
public Set<String> getAllLiterals() {
return allLiterals;
}
//
// LITERAL SIZE REPRESENTS THE MAXIMUM NUMBER OF LITERALS A CLAUSE CAN CONTAIN
//
public int getLiteralSize() {
return literalSize;
}
public void setLiteralSize(int literalSize) {
this.literalSize = literalSize;
}
public String getFilePath() {
return "src/test/ExampleCNFs/" + fileName + ".cnf";
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
//
// HIGHEST LITERAL REPRESENTS THE HIGHEST NUMBER USED TO REPRESENT A LITERAL
// IN THE DIMACS CNF FORMAT
//
public int getHighestLiteral() {
return highestLiteral;
}
public void setHighestLiteral(int highestLiteral) {
this.highestLiteral = highestLiteral;
}
public void setHighestLiteral(String highestLiteral) {
this.highestLiteral = Integer.parseInt(highestLiteral);
}
}
Can someone give me some insight as to what's going on here, please? Thank you very much.
First of all, neither of the symptoms are actually relevant to your question:
A Native method called waitForReferencePendingList() seems to be stuck waiting for something.
You appear to have found an internal thread that is dealing with the processing of Reference objects following a garbage collection. It is normal for it to be waiting there.
IntelliJ tells me "Connected to the target VM, address: '127.0.0.1:51236', transport: 'socket'"
That is Intellij saying that it has connected to the debug agent in the JVM that is running your application. Again, this is normal.
If you are trying to find the cause of a problem via a debugger, you need to find the application thread that is stuck. Then drill down to the point where it is actually stuck and look at the corresponding source code to figure out what it is doing. In this case, you need to look at the standard Java SE library source code for your platform. Randomly looking for clues rarely works ...
Now to your actual problem.
Without a stacktrace or a minimal reproducible example, it is not possible to say with certainty what is happening.
However, I suspect that writeObject is simply stuck waiting for something to read from the "other end" of the pipeline. It looks like you have set up a PipedInputStream / PipedOutputStream pair. This has only a limited amount of buffering. If the "writer" writes too much to the output stream, it will block until the "reader" has read some data from the input stream.
The other weird thing is that the writeObject() method seems to work if I use a different kind of object ...
The other kind of object probably has a smaller serialization which fits into the available buffer space.
This question already has answers here:
Why does the foreach statement not change the element value?
(6 answers)
Closed 5 years ago.
Im new to Java-programming and I just got an assignment at school I'm struggling a bit with. The code you see below is the only code I'm allowed to edit.
The assignment is to find the word "ADJEKTIV" in a txt file and replace it with a random adjective from another txt document containing only adjectives. This part I think I nailed. But when I try to use the write-method from another class called OutputWriter, it seems like it won't take the new updates to the Strings containing "ADJEKTIV". Do I need to "update" the ArrayList somehow to keep the changes?
import java.util.*;
/**
* Class documentation needed!
*/
public class StoryCreator
{
private InputReader reader;
private OutputWriter writer;
private Random random;
public StoryCreator()
{
reader = new InputReader();
writer = new OutputWriter();
random = new Random();
}
public String randomAdjective(String adjectivesFilename)
{
ArrayList<String> adjectives = reader.getWordsInFile(adjectivesFilename);
int index = random.nextInt(adjectives.size());
return adjectives.get(index);
}
public void createAdjectiveStory(String storyFilename, String adjectivesFilename, String outputFilename)
{
ArrayList<String> story = reader.getWordsInFile(storyFilename);
for(String s : story)
{
if(s.contains("ADJEKTIV."))
{
s = randomAdjective(adjectivesFilename) + ". ";
}
if(s.contains("ADJEKTIV"))
{
s = randomAdjective(adjectivesFilename);
}
}
writer.write(story, outputFilename);
}
}
This is the method from the OutputWriter-class:
public void write(ArrayList<String> output, String filename)
{
try {
FileWriter out = new FileWriter(filename);
for(String word : output) {
out.write(word + " ");
}
out.close();
}
catch(IOException exc) {
System.out.println("Error writing output file: " + exc);
}
}
You are not updating the list with
s = randomAdjective(adjectivesFilename);
You instanciate a new String, this is not the instance in the List anymore.
You need to set that value into the list. For that, you need to keep track of the index and use List.set(int, E) to update the list at a specific place.
The easiest at your level. Change the loop to :
for( int i = 0; i < story.size(); i++){
String s = story.get(i);
if(s.contains("ADJEKTIV."))
{
//replace the value with a new one.
s = randomAdjective(adjectivesFilename) + ". ");
story.set(i, s);
/* OR shorter
story.set(i, randomAdjective(adjectivesFilename) + ". ");
*/
}
...
}
Instead of iterating over the ArrayList, you can also replace all the occurrences of once using replaceAll method.
public void createAdjectiveStory(String storyFilename, String adjectivesFilename, String outputFilename)
{
ArrayList<String> story = reader.getWordsInFile(storyFilename);
story.replaceAll(new UnaryOperator<String>() {
public String apply(String original) {
if(original.contains("ADJEKTIV."))
return randomAdjective(adjectivesFilename) + ". ";
if(original.contains("ADJEKTIV"))
return randomAdjective(adjectivesFilename);
return original;
}
});
writer.write(story, outputFilename);
}
So far, I have this project where I read in a properties file using PropertiesConfiguration (from Apache), edit the values I would like to edit, and then save change to the file. It keeps the comments and formatting and such, but one thing it does change is taking the multi-line values formatted like this:
key=value1,\
value2,\
value3
and turns it into the array style:
key=value1,value2,value3
I would like to be able to print those lines formatted as the were before.
I did this via this method:
PropertiesConfiguration config = new PropertiesConfiguration(configFile);
config.setProperty(key,value);
config.save();
I created a work around in case anyone else needs this functionality. Also, there is probably a better way to do this, but this solution currently works for me.
First, set your PropertiesConfiguration delimiter to the new line character like so:
PropertiesConfiguration config = new PropertiesConfiguration(configFile);
config.setListDelimiter('\n');
Then you will need to iterate through and update all properties (to set the format):
Iterator<String> keys = config.getKeys();
while (keys.hasNext()) {
String key = keys.next();
config.setProperty(key,setPropertyFormatter(key, config.getProperty(key))) ;
}
use this method to format your value list data (as shown above):
private List<String> setPropertyFormatter(String key, Object list) {
List<String> tempProperties = new ArrayList<>();
Iterator<?> propertyIterator = PropertyConverter.toIterator(list, '\n');;
String indent = new String(new char[key.length() + 1]).replace('\0', ' ');
Boolean firstIteration = true;
while (propertyIterator.hasNext()) {
String value = propertyIterator.next().toString();
Boolean lastIteration = !propertyIterator.hasNext();
if(firstIteration && lastIteration) {
tempProperties.add(value);
continue;
}
if(firstIteration) {
tempProperties.add(value + ",\\");
firstIteration = false;
continue;
}
if (lastIteration) {
tempProperties.add(indent + value);
continue;
}
tempProperties.add(indent + value + ",\\");
}
return tempProperties;
}
Then it is going to be almost correct, except the save function takes the double backslash that is stored in the List, and turns it into 4 back slashes in the file! So you need to replace those with a single backslash. I did this like so:
try {
config.save(new File(filePath));
byte[] readIn = Files.readAllBytes(Paths.get(filePath));
String replacer = new String(readIn, StandardCharsets.UTF_8).replace("\\\\\\\\", "\\");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath, false), "UTF-8"));
bw.write(replacer);
bw.close();
} catch (ConfigurationException | IOException e) {
e.printStackTrace();
}
With commons-configuration2, you would handle such cases with a custom PropertiesWriter implementation, as described in its documentation under "Custom properties readers and writers" (Reader biased though).
A writer provides a way to govern writing of each character that is to be written to the properties file, so you can achieve pretty much anything you desire with it (via PropertiesWriter.write(String)). There is also a convenient method that writes proper newlines (PropertiesWriter.writeln(String)).
For example, I had to handle classpath entries in a Netbeans Ant project project.properties file:
public class ClasspathPropertiesWriter extends PropertiesConfiguration.PropertiesWriter {
public ClasspathPropertiesWriter(Writer writer, ListDelimiterHandler delimiter) {
super(writer, delimiter);
}
#Override
public void writeProperty(String key, Object value, boolean forceSingleLine) throws IOException {
switch (key) {
case "javac.classpath":
case "run.classpath":
case "javac.test.classpath":
case "run.test.classpath":
String str = (String) value;
String[] split = str.split(":");
if (split.length > 1) {
write(key);
write("=\\");
writeln(null);
for (int i = 0; i < split.length; i++) {
write(" ");
write(split[i]);
if (i != split.length - 1) {
write(":\\");
}
writeln(null);
}
} else {
super.writeProperty(key, value, forceSingleLine);
}
break;
default:
super.writeProperty(key, value, forceSingleLine);
break;
}
}
}
public class CustomIOFactory extends PropertiesConfiguration.DefaultIOFactory {
#Override
public PropertiesConfiguration.PropertiesWriter createPropertiesWriter(
Writer out, ListDelimiterHandler handler) {
return new ClasspathPropertiesWriter(out, handler);
}
}
Parameters params = new Parameters();
FileBasedConfigurationBuilder<Configuration> builder =
new FileBasedConfigurationBuilder<Configuration>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("project.properties")
.setIOFactory(new CustomIOFactory());
Configuration config = builder.getConfiguration();
builder.save();
I have made a class to extract subtrees using Tregex. I used some code snips from "TregexPattern.java", as i don't want to let the program use the console commands.
In general, having a tree for a sentence, I want to extract certain sub tree (no user interaction).
what I did so far is the following:
package edu.stanford.nlp.trees.tregex;
import edu.stanford.nlp.ling.StringLabelFactory;
import edu.stanford.nlp.trees.*;
import java.io.*;
import java.util.*;
public abstract class Test {
abstract TregexMatcher matcher(Tree root, Tree tree, Map<String, Tree> namesToNodes, VariableStrings variableStrings);
public TregexMatcher matcher(Tree t) {
return matcher(t, t, new HashMap<String, Tree>(), new VariableStrings());
}
public static void main(String[] args) throws ParseException, IOException {
String encoding = "UTF-8";
TregexPattern p = TregexPattern.compile("NP < NN & <<DT"); //"/^MWV/" or "NP < (NP=np < NNS)"
TreeReader r = new PennTreeReader(new StringReader("(VP (VP (VBZ Try) (NP (NP (DT this) (NN wine)) (CC and) (NP (DT these) (NNS snails)))) (PUNCT .))"), new LabeledScoredTreeFactory(new StringLabelFactory()));
Tree t = r.readTree();
treebank = new MemoryTreebank();
treebank.add(t);
TRegexTreeVisitor vis = new TRegexTreeVisitor(p, encoding);
**treebank.apply(vis); //line 26**
if (TRegexTreeVisitor.printMatches) {
System.out.println("There were " + vis.numMatches() + " matches in total.");
}
}
private static Treebank treebank; // used by main method, must be accessible
static class TRegexTreeVisitor implements TreeVisitor {
private static boolean printNumMatchesToStdOut = false;
static boolean printNonMatchingTrees = false;
static boolean printSubtreeCode = false;
static boolean printTree = false;
static boolean printWholeTree = false;
static boolean printMatches = true;
static boolean printFilename = false;
static boolean oneMatchPerRootNode = false;
static boolean reportTreeNumbers = false;
static TreePrint tp;
PrintWriter pw;
int treeNumber = 0;
TregexPattern p;
//String[] handles;
int numMatches;
TRegexTreeVisitor(TregexPattern p, String encoding) {
this.p = p;
//this.handles = handles;
try {
pw = new PrintWriter(new OutputStreamWriter(System.out, encoding), true);
} catch (UnsupportedEncodingException e) {
System.err.println("Error -- encoding " + encoding + " is unsupported. Using ASCII print writer instead.");
pw = new PrintWriter(System.out, true);
}
// tp.setPrintWriter(pw);
}
public void visitTree(Tree t) {
treeNumber++;
if (printTree) {
pw.print(treeNumber + ":");
pw.println("Next tree read:");
tp.printTree(t, pw);
}
TregexMatcher match = p.matcher(t);
if (printNonMatchingTrees) {
if (match.find()) {
numMatches++;
} else {
tp.printTree(t, pw);
}
return;
}
Tree lastMatchingRootNode = null;
while (match.find()) {
if (oneMatchPerRootNode) {
if (lastMatchingRootNode == match.getMatch()) {
continue;
} else {
lastMatchingRootNode = match.getMatch();
}
}
numMatches++;
if (printFilename && treebank instanceof DiskTreebank) {
DiskTreebank dtb = (DiskTreebank) treebank;
pw.print("# ");
pw.println(dtb.getCurrentFile());
}
if (printSubtreeCode) {
pw.println(treeNumber + ":" + match.getMatch().nodeNumber(t));
}
if (printMatches) {
if (reportTreeNumbers) {
pw.print(treeNumber + ": ");
}
if (printTree) {
pw.println("Found a full match:");
}
if (printWholeTree) {
tp.printTree(t, pw);
} else {
**tp.printTree(match.getMatch(), pw); //line 108**
}
// pw.println(); // TreePrint already puts a blank line in
} // end if (printMatches)
} // end while match.find()
} // end visitTree
public int numMatches() {
return numMatches;
}
} // end class TRegexTreeVisitor
}
but it give the following error:
Exception in thread "main" java.lang.NullPointerException
at edu.stanford.nlp.trees.tregex.Test$TRegexTreeVisitor.visitTree(Test.java:108)
at edu.stanford.nlp.trees.MemoryTreebank.apply(MemoryTreebank.java:376)
at edu.stanford.nlp.trees.tregex.Test.main(Test.java:26)
Java Result: 1
Any modifications or ideas?
NullPointerException is usually an indicator of bug in software.
I had the same task in the past. Sentence was parsed with dependency parser.
I decided to put resulting parse tree in XML(DOM) and perform XPath queries over it.
To enhance performance you don't need to put xml in String, just keep all XML structure as DOM in memory (e.g. http://www.ibm.com/developerworks/xml/library/x-domjava/).
Using XPath for querying tree-like data structure gave me the following benefits:
Load/Save/Transfer results of sentence parsing easily.
Robust syntax/capabilities of XPath.
Many people know XPath (everyone can customize your query).
XML and XPath are cross platform.
Plenty of stable implementations of XPath and XML/DOM libraries.
Ability to use XSLT.
Integration with existing XML-based pipeline XSLT+XPath -> XSD -> Do actions (e.g. users have specified their email address and action what to do with it somewhere inside of free-text complaint).