Presentation of our ECOOP 2006 paper:
A. KELLENS, K. MENS, J. BRICHAU & K. GYBELS.
Managing the Evolution of Aspect-Oriented Software with Model-based Pointcuts.
Proceedings of the European Conference on Object-Oriented Programming (ECOOP 2006), D. Thomas (Ed.), LNCS 4067, Springer-Verlag, pp. 501–525, 2006.
In spite of the more advanced modularisation mechanisms, aspect-oriented programs still suffer from evolution problems. Due to the fragile pointcut problem, seemingly safe modifications to the base code of an aspect-oriented program can have an unexpected impact on the semantics of the pointcuts defined in that program. This can lead to broken aspect functionality due to accidental join point misses and unintended join point captures. We tackle this problem by declaring pointcuts in terms of a conceptual model of the base program, rather than defining them directly in terms of how the base program is structured. As such, we achieve an effective decoupling of the pointcuts from the base program’s structure. In addition, the conceptual model provides a means to verify where and why potential fragile pointcut conflicts occur, by imposing structural and semantic constraints on the conceptual model, that can be verified when the base program evolves. To validate our approach we implemented a model-based pointcut mechanism, which we used to define some aspects on SmallWiki, a medium-sized application, and subsequently detected and resolved occurrences of the fragile pointcut problem when this application evolved.
Managing the Evolution of Aspect-Oriented Software with Model-based Pointcuts
1. Managing the Evolution
of Aspect-Oriented Software
with Model-based Pointcuts
Andy Kellens Kim Mens Johan Brichau Kris Gybels
1
2. Crosscutting Concerns
*/
public class FileDownload {
Synchronisation
public static void download(String address, String localFileName) {
OutputStream out = null;
URLConnection conn = null;
InputStream in = null;
try {
URL url = new URL(address);
out = new BufferedOutputStream(
new FileOutputStream(localFileName));
import java.io.*;
import java.util.zip.*;
conn = url.openConnection();
in = conn.getInputStream();
/**
* Command line program to copy a file to another directory.
byte[] buffer = new byte[1024];
* @author Marco Schmidt
int numRead;
*/
public class CopyFile {
long numWritten = 0;
// constant values for the override option
while ((numRead = in.read(buffer)) != -1) {
public static final int OVERWRITE_ALWAYS = 1;
public static final int OVERWRITE_NEVER = 2;
out.write(buffer, 0, numRead);
public static final int OVERWRITE_ASK = 3;
numWritten += numRead;
// program options initialized to default values
}
private static int bufferSize = 4 * 1024;
System.out.println(localFileName + quot;tquot; + numWritten);
private static boolean clock = true;
private static boolean copyOriginalTimestamp = true;
} catch (Exception exception) {
private static boolean verify = true;
exception.printStackTrace();
private static int override = OVERWRITE_ASK;
} finally {
public static Long copyFile(File srcFile, File destFile)
try {
throws IOException {
InputStream in = new FileInputStream(srcFile);
if (in != null) {
OutputStream out = new FileOutputStream(destFile);
in.close();
long millis = System.currentTimeMillis();
UI dependency
CRC32 checksum = null;
}
if (verify) {
if (out != null) {
checksum = new CRC32();
checksum.reset();
out.close();
}
}
byte[] buffer = new byte[bufferSize];
int bytesRead;
} catch (IOException ioe) {
while ((bytesRead = in.read(buffer)) >= 0) {
}
if (verify) {
checksum.update(buffer, 0, bytesRead);
}
}
}
out.write(buffer, 0, bytesRead);
}
out.close();
public static void download(String address) {
in.close();
if (clock) {
int lastSlashIndex = address.lastIndexOf('/');
millis = System.currentTimeMillis() - millis;
if (lastSlashIndex >= 0 &&
System.out.println(quot;Second(s): quot; + (millis/1000L));
}
lastSlashIndex < address.length() - 1) {
if (verify) {
download(address, address.substring(lastSlashIndex + 1));
return new Long(checksum.getValue());
} else {
} else {
return null;
System.err.println(quot;Could not figure out local file name for quot; +
}
}
address);
}
public static Long createChecksum(File file) throws IOException {
long millis = System.currentTimeMillis();
}
InputStream in = new FileInputStream(file);
CRC32 checksum = new CRC32();
checksum.reset();
public static void main(String[] args) {
byte[] buffer = new byte[bufferSize];
for (int i = 0; i < args.length; i++) {
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
download(args[i]);
checksum.update(buffer, 0, bytesRead);
}
}
in.close();
}
if (clock) {
}
millis = System.currentTimeMillis() - millis;
System.out.println(quot;Second(s): quot; + (millis/1000L));
}
return new Long(checksum.getValue());
}
/**
*/
* Determine if data is to be copied to given file.
public class HappyNewYear implements Runnable
* Take into consideration override option and
* ask user in case file exists and override option is ask.
{
* @param file File object for potential destination file
private static NumberFormat formatter = NumberFormat.getInstance();
* @return true if data is to be copied to file, false if not
*/
private JFrame frame;
public static boolean doCopy(File file) {
private JLabel label;
boolean exists = file.exists();
if (override == OVERWRITE_ALWAYS || !exists) {
private long newYearMillis;
return true;
private String message;
} else
if (override == OVERWRITE_NEVER) {
return false;
public HappyNewYear(JFrame frame, JLabel label)
} else
if (override == OVERWRITE_ASK) {
{
return readYesNoFromStandardInput(quot;File exists. quot; +
// store argument GUI elements
quot;Overwrite (y/n)?quot;);
} else {
this.frame = frame;
throw new InternalError(quot;Program error. Invalid quot; +
this.label = label;
quot;value for override: quot; + override);
}
// compute beginning of next year
}
Calendar cal = new GregorianCalendar();
public static void main(String[] args) throws IOException {
int nextYear = cal.get(Calendar.YEAR) + 1;
// make sure there are exactly two arguments
cal.set(Calendar.YEAR, nextYear);
if (args.length != 2) {
System.err.println(quot;Usage: CopyFile SRC-FILE-NAME DEST-DIR-NAMEquot;);
cal.set(Calendar.MONTH, Calendar.JANUARY);
System.exit(1);
cal.set(Calendar.DAY_OF_MONTH, 1);
}
// make sure the source file is indeed a readable file
cal.set(Calendar.HOUR_OF_DAY, 0);
File srcFile = new File(args[0]);
cal.set(Calendar.MINUTE, 0);
if (!srcFile.isFile() || !srcFile.canRead()) {
System.err.println(quot;Not a readable file: quot; + srcFile.getName());
cal.set(Calendar.SECOND, 0);
System.exit(1);
newYearMillis = cal.getTime().getTime();
}
// make sure the second argument is a directory
Scattering
// prepare a message
File destDir = new File(args[1]);
message = quot;Happy quot; + nextYear + quot;!quot;;
if (!destDir.isDirectory()) {
System.err.println(quot;Not a directory: quot; + destDir.getName());
}
System.exit(1);
}
// create File object for destination file
public static int determineFontSize(JFrame frame,
File destFile = new File(destDir, srcFile.getName());
int componentWidth, String fontName, int fontStyle,
// check if copying is desired given overwrite option
String text)
if (!doCopy(destFile)) {
{
return;
}
int fontSize = componentWidth * 2 / text.length();
Font font = new Font(fontName, fontStyle, fontSize);
// copy file, optionally creating a checksum
Long checksumSrc = copyFile(srcFile, destFile);
FontMetrics fontMetrics = frame.getGraphics().
&
getFontMetrics(font);
// copy timestamp of last modification
if (copyOriginalTimestamp) {
int stringWidth = fontMetrics.stringWidth(text);
if (!destFile.setLastModified(srcFile.lastModified())) {
return (int)(fontSize * 0.95 *
System.err.println(quot;Error: Could not set quot; +
quot;timestamp of copied file.quot;);
componentWidth / stringWidth);
}
}
}
// optionally verify file
public static void main(String[] args)
if (verify) {
System.out.print(quot;Verifying destination file...quot;);
{
Long checksumDest = createChecksum(destFile);
JFrame frame = new JFrame();
if (checksumSrc.equals(checksumDest)) {
System.out.println(quot; OK, files are equal.quot;);
frame.addKeyListener(new KeyListener()
} else {
{
System.out.println(quot; Error: Checksums differ.quot;);
Tangling
}
public void keyPressed(KeyEvent event) {}
}
public void keyReleased(KeyEvent event) {
}
if (event.getKeyChar() == KeyEvent.VK_ESCAPE)
/**
{
* Print a message to standard output and read lines from
* standard input until yes or no (y or n) is entered.
System.exit(0);
* @param message informative text to be answered by user
}
* @return user answer, true for yes, false for no.
*/
}
public static boolean readYesNoFromStandardInput(String message) {
public void keyTyped(KeyEvent event) {}
System.out.println(message);
String line;
}
BufferedReader in = new BufferedReader(new InputStreamReader(
);
System.in));
Boolean answer = null;
frame.setUndecorated(true);
try
JLabel label = new JLabel(quot;.quot;);
{
while ((line = in.readLine()) != null) {
label.setBackground(Color.BLACK);
line = line.toLowerCase();
label.setForeground(Color.WHITE);
if (quot;yquot;.equals(line) || quot;yesquot;.equals(line)) {
answer = Boolean.TRUE;
label.setOpaque(true);
break;
label.setHorizontalAlignment(SwingConstants.CENTER);
}
else
frame.getContentPane().add(label);
if (quot;nquot;.equals(line) || quot;noquot;.equals(line)) {
GraphicsEnvironment.getLocalGraphicsEnvironment().
answer = Boolean.FALSE;
break;
getDefaultScreenDevice().setFullScreenWindow(frame);
}
final int fontStyle = Font.BOLD;
else
{
final String fontName = quot;SansSerifquot;;
System.out.println(quot;Could not understand answer (quot;quot; +
int fontSizeNumber = determineFontSize(frame,
line + quot;quot;). Please use y for yes or n for no.quot;);
}
Toolkit.getDefaultToolkit().getScreenSize().width,
}
fontName, fontStyle, formatter.format(88888888));
if (answer == null) {
throw new IOException(quot;Unexpected end of input from stdin.quot;);
int fontSizeText = determineFontSize(frame,
}
Toolkit.getDefaultToolkit().getScreenSize().width,
in.close();
return answer.booleanValue();
fontName, fontStyle, quot;Happy 8888!quot;);
}
label.setFont(new Font(fontName, fontStyle,
catch (IOException ioe)
{
Math.min(fontSizeNumber, fontSizeText)));
throw new InternalError(
new HappyNewYear(frame, label).run();
quot;Cannot read from stdin or write to stdout.quot;);
}
}
}
}
public void run()
{
boolean newYear = false;
do
{
long time = System.currentTimeMillis();
long remaining = (newYearMillis - time) / 1000L;
String output;
if (remaining < 1)
{
// new year!
newYear = true;
output = message;
}
else
{
// make a String from the number of seconds
output = formatter.format(remaining);
}
label.setText(output);
try
{
Thread.sleep(1000);
}
}
2