Removal of package declaration in Java

Removal of package declaration in Java

by Antoni Oliver -
Number of replies: 4

Hello.

We will use this to grade rather simple Java projects that will have no need for the package system.

I've seen two threads here that dealt with very complex solutions that attempted to implement the package system.

My question is: is there a simple way to just remove all package declarations?

Like the last line in this message:

{{ STUDENT_ANSWER | replace({'package foobar;': ''}) | replace({'public class ': 'class ' }) }}

I tried doing this and failed.

Thank you.

In reply to Antoni Oliver

Re: Removal of package declaration in Java

by Richard Lobb -

What fails exactly?

If you use the line

{{ STUDENT_ANSWER | replace({'package foobar;': ''}) | replace({'public class ': 'class ' }) }}

as the body of the template, then you'll have stripped the public class from the student answer which will then defeat the name fiddling that tries to name the student answer according to the name of their main class.

But what about:

{{ STUDENT_ANSWER | replace({'package foobar;': ''}) }}

?

Assuming, of course, that students have been asked to declare their code as being in a package foobar. And that they're pasting their answer into the main answer box, not as an attachment.

If you're trying to do any clever editing of student's code, I do recommend that you switch to using a Python-scripted question type that gives you complete control over the whole edit/compile/run process, including dealing with any attachments.

In reply to Richard Lobb

Re: Removal of package declaration in Java

by Antoni Oliver -

Hi, of course this is related to the other question.

So, the IDE we use automatically sets up the package for the project, which is unnecessary for these kind of assignments.

I think the issue with that code was the spaces at both sides of the pipe:

{{ STUDENT_ANSWER|replace({'package': '// package'}) }}

This apparently works.

Do you think this is a bad idea if I'm using a java_program question type? I don't expect them to submit more than a single class in the answer box (but I expect them to provide multiple (0+) files as attachments, one class in each.

Also, they'll import some code from the Java API.

The removal of packages would only be an issue if they wanted to distinguish between java.lang.String and my.package.String, right?

Thank you!

EDIT: this replace does not work in the attachments! Agh! Any workaround there?

In reply to Antoni Oliver

Re: Removal of package declaration in Java

by Antoni Oliver -

Well, in the end I rolled up my sleeves and created a python template as you suggested.

I first faced an issue trying to write to files owned by apache (as jobe is executed as jobe), so I decided to place all the source in a src directory, place the compiled classes in dist and then execute.

I try to obtain the class name for the text in the answer box and, if a file is supplied with that name, it gets overwritten.

e.g.: the answer box is prefilled with a public class Main {} but the student submits their Main.java which is the one which they expect to be executed.

I comment the package declaration from every file and then execute the class named Main (as per the other question).

I think everything works. Do you see anything weird here?

Thank you!

import os
import subprocess
import sys
import re

# The files that are going to be compiled.
files = []

# We create a src directory to place the (edited) source files.
# This is because we cannot write to the original source files
# for permission issues.
os.mkdir('src')

# We infer the submitted answer class name.
student_answer = """{{ STUDENT_ANSWER | e('py') }}"""
match = re.search(r'class (.+?)[\W{]', student_answer)
if match:
classname = match.group(1)
student_answer = student_answer.replace('package', '// package')
out = os.path.join('src', classname + '.java')
with open(out, 'w') as file:
file.write(student_answer)
files.append(out)

# We remove the package declaration from all the files.
for f in os.listdir():
if f.endswith('.java'):
with open(f, 'r') as file:
code = file.read()
code = code.replace('package', '// package')
out = os.path.join('src', f)
with open(out, 'w') as file:
file.write(code)
files.append(out)

# Compile
return_code = subprocess.call(['javac', '-d', 'dist'] + files)

if return_code != 0:
print('** Error de compilació **\nComprova el missatge d\'error i torna a provar-ho.', file=sys.stderr)
else:
# Execute
subprocess.call(['java', '-cp', 'dist', 'Main'])
In reply to Antoni Oliver

Re: Removal of package declaration in Java

by Richard Lobb -

Wow, you've had a busy day while I was asleep!

That's exactly the sort of thing I would have recommended if you hadn't beaten me to it. Well done!

The files initially loaded into the working directory are all write protected for security, so that student code can't corrupt question-author-supplied files. So indeed you needed to create yourself a separate directory first.

My Java's pretty rusty, but it all seems OK to me. The documentation for -d says the directory must already exists but you haven't created the 'dist' directory explicitly. I assume the documentation is out of date?

The code assumes the main class is called Main. Another option might be to assume that if the student provides a class in the answer box, then that is the main class?

If you haven't already done so, I suggest making that into a new question type. Then, over time, you add template parameters to add functionality, such as checking for required constructs in student code, style checks, etc.

I assume you've discovered that you can set the Ace language separately from the template language, so students get Java syntax colouring?

Something to consider for the future is whether you could profit from a combinator template. You've written a per-test template, and that's how the JavaProgram  question type usually behaves anyway; by default, even with combinator templates, CodeRunner drops back to one-test-at-a-time mode when each test has its own standard input. But that means you edit and recompile the student code for each test. If you instead used a combinator template with the Allow multiple studins box checked, too, you could get by with just a single compile. Assume the compile works, you would then loop to run each test, setting up standard input from the TEST.stdin field. You need to insert the SEPARATOR string between outputs.

However, that's probably more complexity than you want right now, and all it does is speed up testing a bit.