Skip to main content
Code Review

Return to Question

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

This is a follow-up question to Tool for creating CodeReview questions.

This is a follow-up question to Tool for creating CodeReview questions.

deleted 30 characters in body; edited title
Source Link
Simon Forsberg
  • 59.7k
  • 9
  • 157
  • 311

Follow-up to tool for posting Code ReviewCodeReview questions

You can now use the tool directly by downloading the jar-file from GitHub the jar-file from GitHub and running it with one of the following options:

Follow-up to tool for posting Code Review questions

You can now use the tool directly by downloading the jar-file from GitHub and running it with one of the following options:

Follow-up to tool for posting CodeReview questions

You can now use the tool directly by downloading the jar-file from GitHub and running it with one of the following options:

Tweeted twitter.com/#!/StackCodeReview/status/432145231912648704
Source Link
Simon Forsberg
  • 59.7k
  • 9
  • 157
  • 311

Follow-up to tool for posting Code Review questions

#Description

This is a follow-up question to Tool for creating CodeReview questions.

Things that has changed include:

  • Removed replacing four spaces with one tab, all tabs and all spaces in the code itself is now left as-is.
  • Added file extensions to the output.
  • Switched order of lines and bytes as I feel that the number of lines of code is more interesting than the number of bytes.
  • Support for command-line parameters to directly process a directory or a bunch of files, with the support for wildcards. If a directory or wildcard is used, files that don't pass an ASCII-content check gets skipped. If you have specified a file that has a lot of non-ASCII content it is processed anyway.

I am asking for another review because of the things that I have added mostly, see the questions below.

###Class Summary (413 lines in 4 files, making a total of 12134 bytes)

  • CountingStream.java: OutputStream that keeps track on the number of written bytes to it
  • ReviewPrepareFrame.java: JFrame for letting user select files that should be up for review
  • ReviewPreparer.java: The most important class, takes care of most of the work. Expects a List of files in the constructor and an OutputStream when called.
  • TextAreaOutputStream.java: OutputStream for outputting to a JTextArea.

#Code

The code can also be found on GitHub

CountingStream.java: (27 lines, 679 bytes)

/**
 * An output stream that keeps track of how many bytes that has been written to it.
 */
public class CountingStream extends FilterOutputStream {
 private final AtomicInteger bytesWritten;
 
 public CountingStream(OutputStream out) {
 super(out);
 this.bytesWritten = new AtomicInteger();
 }
 
 @Override
 public void write(int b) throws IOException {
 bytesWritten.incrementAndGet();
 super.write(b);
 }
 public int getBytesWritten() {
 return bytesWritten.get();
 }
}

ReviewPrepareFrame.java: (112 lines, 3255 bytes)

public class ReviewPrepareFrame extends JFrame {
 private static final long serialVersionUID = 2050188992596669693L;
 private JPanel contentPane;
 private final JTextArea result = new JTextArea();
 /**
 * Launch the application.
 */
 public static void main(String[] args) {
 if (args.length == 0) {
 EventQueue.invokeLater(new Runnable() {
 public void run() {
 try {
 new ReviewPrepareFrame().setVisible(true);
 }
 catch (Exception e) {
 e.printStackTrace();
 }
 }
 });
 }
 else ReviewPreparer.main(args);
 }
 /**
 * Create the frame.
 */
 public ReviewPrepareFrame() {
 setTitle("Prepare code for Code Review");
 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 setBounds(100, 100, 450, 300);
 contentPane = new JPanel();
 contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
 contentPane.setLayout(new BorderLayout(0, 0));
 setContentPane(contentPane);
 
 JPanel panel = new JPanel();
 contentPane.add(panel, BorderLayout.NORTH);
 
 final DefaultListModel<File> model = new DefaultListModel<>();
 final JList<File> list = new JList<File>();
 panel.add(list);
 list.setModel(model);
 
 JButton btnAddFiles = new JButton("Add files");
 btnAddFiles.addActionListener(new ActionListener() {
 public void actionPerformed(ActionEvent e) {
 JFileChooser dialog = new JFileChooser();
 dialog.setMultiSelectionEnabled(true);
 if (dialog.showOpenDialog(ReviewPrepareFrame.this) == JFileChooser.APPROVE_OPTION) {
 for (File file : dialog.getSelectedFiles()) {
 model.addElement(file);
 }
 }
 }
 });
 panel.add(btnAddFiles);
 
 JButton btnRemoveFiles = new JButton("Remove files");
 btnRemoveFiles.addActionListener(new ActionListener() {
 public void actionPerformed(ActionEvent e) {
 for (File file : new ArrayList<>(list.getSelectedValuesList())) {
 model.removeElement(file);
 }
 }
 });
 panel.add(btnRemoveFiles);
 
 JButton performButton = new JButton("Create Question stub with code included");
 performButton.addActionListener(new ActionListener() {
 public void actionPerformed(ActionEvent arg0) {
 result.setText("");
 ReviewPreparer preparer = new ReviewPreparer(filesToList(model));
 TextAreaOutputStream outputStream = new TextAreaOutputStream(result);
 preparer.createFormattedQuestion(outputStream);
 }
 });
 contentPane.add(performButton, BorderLayout.SOUTH);
 contentPane.add(result, BorderLayout.CENTER);
 }
 public List<File> filesToList(DefaultListModel<File> model) {
 List<File> files = new ArrayList<>();
 for (int i = 0; i < model.getSize(); i++) {
 files.add(model.get(i));
 }
 return files;
 }
 
}

ReviewPreparer.java: (233 lines, 7394 bytes)

public class ReviewPreparer {
 public static double detectAsciiness(File input) throws IOException {
 if (input.length() == 0)
 return 0;
 try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(input)))) {
 int read;
 long asciis = 0;
 char[] cbuf = new char[1024];
 while ((read = reader.read(cbuf)) != -1) {
 for (int i = 0; i < read; i++) {
 char c = cbuf[i];
 if (c <= 0x7f)
 asciis++;
 }
 }
 return asciis / (double) input.length();
 }
 }
 private final List<File> files;
 public ReviewPreparer(List<File> files) {
 this.files = new ArrayList<>();
 
 for (File file : files) {
 if (file.getName().lastIndexOf('.') == -1)
 continue;
 if (file.length() < 10)
 continue;
 
 this.files.add(file);
 }
 }
 public int createFormattedQuestion(OutputStream out) {
 CountingStream counter = new CountingStream(out);
 PrintStream ps = new PrintStream(counter);
 outputHeader(ps);
 outputFileNames(ps);
 outputFileContents(ps);
 outputDependencies(ps);
 outputFooter(ps);
 ps.print("Question Length: ");
 ps.println(counter.getBytesWritten());
 return counter.getBytesWritten();
 }
 private void outputFooter(PrintStream ps) {
 ps.println("#Usage / Test");
 ps.println();
 ps.println();
 ps.println("#Questions");
 ps.println();
 ps.println();
 ps.println();
 }
 private void outputDependencies(PrintStream ps) {
 List<String> dependencies = new ArrayList<>();
 for (File file : files) {
 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
 String line;
 while ((line = in.readLine()) != null) {
 if (!line.startsWith("import ")) continue;
 if (line.startsWith("import java.")) continue;
 if (line.startsWith("import javax.")) continue;
 String importStatement = line.substring("import ".length());
 importStatement = importStatement.substring(0, importStatement.length() - 1); // cut the semicolon
 dependencies.add(importStatement);
 }
 }
 catch (IOException e) {
 ps.println("Could not read " + file.getAbsolutePath());
 ps.println();
 // more detailed handling of this exception will be handled by another function
 }
 
 }
 if (!dependencies.isEmpty()) {
 ps.println("#Dependencies");
 ps.println();
 for (String str : dependencies)
 ps.println("- " + str + ": ");
 }
 ps.println();
 }
 private int countLines(File file) throws IOException {
 return Files.readAllLines(file.toPath(), StandardCharsets.UTF_8).size();
 }
 
 private void outputFileContents(PrintStream ps) {
 ps.println("#Code");
 ps.println();
 ps.println("This code can also be downloaded from [somewhere](http://github.com repository perhaps?)");
 ps.println();
 for (File file : files) {
 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
 int lines = -1;
 try {
 lines = countLines(file);
 }
 catch (IOException e) { 
 }
 ps.printf("**%s:** (%d lines, %d bytes)", file.getName(), lines, file.length());
 
 ps.println();
 ps.println();
 String line;
 int importStatementsFinished = 0;
 while ((line = in.readLine()) != null) {
 // skip package and import declarations
 if (line.startsWith("package ")) 
 continue;
 if (line.startsWith("import ")) {
 importStatementsFinished = 1;
 continue;
 }
 if (importStatementsFinished >= 0) importStatementsFinished = -1;
 if (importStatementsFinished == -1 && line.trim().isEmpty()) // skip empty lines directly after import statements 
 continue;
 importStatementsFinished = -2;
 ps.print(" "); // format as code for StackExchange, this needs to be four spaces.
 ps.println(line);
 }
 }
 catch (IOException e) {
 ps.print("> Unable to read " + file + ": "); // use a block-quote for exceptions
 e.printStackTrace(ps);
 }
 ps.println();
 }
 }
 private void outputFileNames(PrintStream ps) {
 int totalLength = 0;
 int totalLines = 0;
 for (File file : files) {
 totalLength += file.length();
 try {
 totalLines += countLines(file);
 }
 catch (IOException e) {
 ps.println("Unable to determine line count for " + file.getAbsolutePath());
 }
 }
 ps.printf("###Class Summary (%d lines in %d files, making a total of %d bytes)", totalLines, files.size(), totalLength);
 ps.println();
 ps.println();
 for (File file : files) {
 ps.println("- " + file.getName() + ": ");
 }
 ps.println();
 }
 
 private void outputHeader(PrintStream ps) {
 ps.println("#Description");
 ps.println();
 ps.println("- Add some [description for what the code does](http://meta.codereview.stackexchange.com/questions/1226/code-should-include-a-description-of-what-the-code-does)");
 ps.println("- Is this a follow-up question? Answer [What has changed, Which question was the previous one, and why you are looking for another review](http://meta.codereview.stackexchange.com/questions/1065/how-to-post-a-follow-up-question)");
 ps.println();
 }
 
 public static boolean isAsciiFile(File file) {
 try {
 return detectAsciiness(file) >= 0.99;
 }
 catch (IOException e) {
 return true; // if an error occoured, we want it to be added to a list and the error shown in the output
 }
 }
 
 public static void main(String[] args) {
 List<File> files = new ArrayList<>();
 if (args.length == 0)
 files.addAll(fileList("."));
 for (String arg : args) {
 files.addAll(fileList(arg));
 }
 new ReviewPreparer(files).createFormattedQuestion(System.out);
 }
 
 public static List<File> fileList(String pattern) {
 List<File> files = new ArrayList<>();
 
 File file = new File(pattern);
 if (file.exists()) {
 if (file.isDirectory()) {
 for (File f : file.listFiles())
 if (!f.isDirectory() && isAsciiFile(f))
 files.add(f);
 }
 else files.add(file);
 }
 else {
 // extract path
 int lastSeparator = pattern.lastIndexOf('\\');
 lastSeparator = Math.max(lastSeparator, pattern.lastIndexOf('/'));
 String path = lastSeparator < 0 ? "." : pattern.substring(0, lastSeparator);
 file = new File(path); 
 
 // path has been extracted, check if path exists
 if (file.exists()) {
 // create a regex for searching for files, such as *.java, Test*.java
 String regex = lastSeparator < 0 ? pattern : pattern.substring(lastSeparator + 1);
 regex = regex.replaceAll("\\.", "\\.").replaceAll("\\?", ".?").replaceAll("\\*", ".*");
 for (File f : file.listFiles()) {
 // loop through directory, skip directories and filenames that don't match the pattern
 if (!f.isDirectory() && f.getName().matches(regex) && isAsciiFile(f)) {
 files.add(f);
 }
 }
 }
 else System.out.println("Unable to find path " + file);
 }
 return files;
 }
}

TextAreaOutputStream.java: (41 lines, 806 bytes)

public class TextAreaOutputStream extends OutputStream {
 private final JTextArea textArea;
 private final StringBuilder sb = new StringBuilder();
 public TextAreaOutputStream(final JTextArea textArea) {
 this.textArea = textArea;
 }
 @Override
 public void flush() {
 }
 @Override
 public void close() {
 }
 @Override
 public void write(int b) throws IOException {
 if (b == '\n') {
 final String text = sb.toString() + "\n";
 SwingUtilities.invokeLater(new Runnable() {
 public void run() {
 textArea.append(text);
 }
 });
 sb.setLength(0);
 return;
 }
 sb.append((char) b);
 }
}

#Usage / Test

You can now use the tool directly by downloading the jar-file from GitHub and running it with one of the following options:

  • java -jar ReviewPrepare.jar runs the Swing form to let you choose files using a GUI.
  • java -jar ReviewPrepare.jar . runs the program in the current working directory and outputting to stdout.
  • java -jar ReviewPrepare.jar . > out.txt runs the program in the current working directory and outputting to the file out.txt (I used this to create this question)
  • java -jar ReviewPrepare.jar C:/some/path/*.java > out.txt runs the program in the specified directory, matching all *.java files and outputting to the file out.txt

#Questions

My main concern currently is with the way I implemented the command line parameters, could it be done easier? (Preferably without using an external library as I would like my code to be independent if possible, although library suggestions for this is also welcome) Is there any common file-pattern-argument that I missed?

I'm also a bit concerned with the extensibility of this, right now it feels not extensible at all. What if someone would want to add custom features for the way Python/C#/C++/etc. files are formatted? Then hard-coding the "scan for imports" in the way I have done it doesn't feel quite optimal.

General reviews are also of course welcome.

lang-java

AltStyle によって変換されたページ (->オリジナル) /