CodeSection.java
package de.uka.ipd.sdq.beagle.core;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.io.File;
import java.io.Serializable;
/**
* Describes a section in examined software’s source code. Code sections may span multiple
* methods, types and compilation units. They are defined by a start statement in one
* source code file and an end statement in another source code file that may or may not
* be the same as the first one. A statement is identified by the index of the statement’s
* first character in its source file. Code sections are immutable, meaning that once
* created, their attributes cannot be changed.
*
* <p>A code section must describe a continuous part of source code, meaning that the last
* statement in the section will always be executed if the first one was executed, given
* that no Exceptions occur. Furthermore, if the last statement is not the same as the
* first one, the last statement may only be executed after the first one was. All
* statements that may potentially be executed after the first statement but before the
* last one are considered to be “in” the section. A code section is considered to have
* been “completely executed” if the section’s first and last statement were executed.
* This does generally not mean that all statements in the section were executed. Code
* Sections no fulfilling these requirements must not be created. However, this is not
* checked at run time, because doing so would solve the halting problem.
*
* @author Joshua Gleitze
* @author Roman Langrehr
*/
public class CodeSection implements Serializable {
/**
* Serialisation version UID, see {@link java.io.Serializable}.
*/
private static final long serialVersionUID = -1823330022448293103L;
/**
* The java file which contains the {@link #startStatementNumber} of this code
* section.
*/
private final File startFile;
/**
* This code section’s first statement’s first character’s index in {@link #startFile}
* , starting with {@code 0}.
*/
private final int startStatementNumber;
/**
* The java file which contains the {@link #endStatementNumber} of this code section.
*/
private final File endFile;
/**
* This code section’s last statement’s first character’s index in {@link #endFile},
* starting with {@code 0}.
*/
private final int endStatementNumber;
/**
* Creates a code section that spans from the statements starting at
* {@code startIndex} in {@code startFile} to the statement starting at
* {@code endIndex} in {@code endFile}.
*
* @param startFile The file containing this section’s first statement. Must not be
* {@code null} and {@link File#isFile() startFile.isFile()} must return
* {@code true}.
* @param startIndex The index of this section’s first statement’s first character in
* {@code startFile}. Counting starts at {@code 0}.
* @param endFile The file containing this section’s last statement. Must not be
* {@code null} and {@link File#isFile() endFile.isFile()} must return
* {@code true}.
* @param endIndex The index of this section’s last statement’s first character in
* {@code endFile}. Counting starts at {@code 0}.
* @throws IllegalArgumentException When {@code startFile.isFile()} or
* {@code endFile.isFile()} returned {@code false}.
* @throws RuntimeException If {@code startFile} or {@code endFile} cannot be read.
*/
public CodeSection(final File startFile, final int startIndex, final File endFile, final int endIndex) {
if (!startFile.isFile()) {
throw new IllegalArgumentException("The given startFile is not a file: " + startFile.getAbsolutePath());
}
if (!endFile.isFile()) {
throw new IllegalArgumentException("The given endFile is not a file: " + endFile.getAbsolutePath());
}
Validate.isTrue(startIndex >= 0, "The startIndex must be non-neagtive, but was %d", startIndex);
Validate.isTrue(endIndex >= 0, "The endIndex must be non-neagtive, but was %d", endIndex);
final long startFileChars = this.countChars(startFile);
final long endFileChars = this.countChars(endFile);
Validate.isTrue(startIndex < startFileChars,
"The startIndex was not in the startFile. It was %d, but the file’s size was %d.", startIndex,
startFileChars);
Validate.isTrue(endIndex < startFileChars,
"The endIndex was not in the endFile. It was %d, but the file’s size was %d.", endIndex, endFileChars);
this.startFile = startFile;
this.startStatementNumber = startIndex;
this.endFile = endFile;
this.endStatementNumber = endIndex;
}
@Override
public boolean equals(final Object object) {
if (object == null) {
return false;
}
if (object == this) {
return true;
}
if (object.getClass() != this.getClass()) {
return false;
}
final CodeSection other = (CodeSection) object;
return new EqualsBuilder().append(this.startFile, other.startFile)
.append(this.startStatementNumber, other.startStatementNumber)
.append(this.endFile, other.endFile)
.append(this.endStatementNumber, other.endStatementNumber)
.isEquals();
}
/**
* Gets the file that contains this section’s last statement.
*
* @return The file containing this section’s last statement. Will never be
* {@code null} and {@code getEndFile().isFile()} always returns {@code true}.
*/
public File getEndFile() {
return this.endFile;
}
/**
* Gets the index of the first character of the last statement in this code section.
* Counting starts at 0. The number thus describes how many characters precede the
* section’s last statement in the {@linkplain #getEndFile() end source code file}.
*
* @return The last statement’s index. A non-negative integer.
*/
public int getEndSectionIndex() {
return this.endStatementNumber;
}
/**
* Gets the file that contains this section’s first statement.
*
* @return The file containing this section’s first statement. Will never be
* {@code null} and {@code getStartFile().isFile()} always returns
* {@code true}.
*/
public File getStartFile() {
return this.startFile;
}
/**
* Gets the index of the first character of the last statement in this code section.
* Counting starts at 0. The number thus describes how many characters precede the
* section’s last statement in the {@linkplain #getEndFile() end source code file}.
*
* @return The first statement’s index. A non-negative integer.
*/
public int getStartSectionIndex() {
return this.startStatementNumber;
}
@Override
public int hashCode() {
// you pick a hard-coded, randomly chosen, non-zero, odd number
// ideally different for each class
return new HashCodeBuilder(23, 45).append(this.startFile)
.append(this.startStatementNumber)
.append(this.endFile)
.append(this.endStatementNumber)
.toHashCode();
}
@Override
public String toString() {
final String startFileName = this.startFile.getName().replace(".java", "");
final String endFileName =
this.startFile.equals(this.endFile) ? "" : this.endFile.getName().replace(".java", "") + ":";
return String.format("%s:%d–%s%d", startFileName, this.startStatementNumber, endFileName,
this.endStatementNumber);
}
/**
* Reads the number of characters in a text-file.
*
* @param file The file to read.
* @return The number of bytes in this file. The number of bytes is returned as this
* is, for most source code files, a good approximation for the number of
* characters and the files don't need to be read for this. This approximation
* never produces false errors.
*/
private long countChars(final File file) {
return file.length();
}
}