/*
(c) SIB Visions 2012
http://www.sibvisions.com
*/
public class SimpleSourceFile
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class members
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** the source parser. */
private ASTParser parser;
/** the parsed tokens of the source file. */
private CompilationUnit cuSource;
/** the original document. */
private Document docOrig;
/** the class type info. */
private TypeDeclaration tdClass;
/** the javadoc of the class. */
private JavadocInfo jdoc;
/** the full qualified name of the class. */
private String sClassName;
/** the full qualified name of the super class after loading. */
private String sLoadedSuperClass;
/** the full qualified name of the parent class. */
private String sSuperClass;
/** whether the source is changed. */
private boolean bChanged = false;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Initialization
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Creates a new instance of <code>SimpleSourceFile</code> without source
* information.
*/
public SimpleSourceFile() {
load("");
}
/**
* Creates a new instance of <code>SimpleSourceFile</code> with a given
* source file.
*
* @param pFile the java source file
* @throws IOException if an error occurs during reading the file
*/
public SimpleSourceFile(File pFile) throws IOException {
if (pFile.exists()) {
JavaSource source = JavaSource.get(pFile.getAbsolutePath());
if (source != null) {
synchronized(source) {
load(new String(FileUtil.getContent(pFile)));
}
} else {
load(new String(FileUtil.getContent(pFile)));
}
} else {
load("");
}
}
/**
* Creates a new instance of <code>SimpleSourceFile</code> with a given
* source stream. The stream is closed after reading
*
* @param pStream the java source stream
* @throws IOException if an error occurs during reading the stream
*/
public SimpleSourceFile(InputStream pStream) throws IOException {
this(pStream, true);
}
/**
* Creates a new instance of <code>SimpleSourceFile</code> with a given
* source stream.
*
* @param pStream the java source stream
* @param pAutoClose whether the stream should be closed after reading
* @throws IOException if an error occurs during reading the stream
*/
public SimpleSourceFile(InputStream pStream, boolean pAutoClose) throws IOException {
load(new String(FileUtil.getContent(pStream, pAutoClose)));
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// User-defined methods
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Loads the properties from the source file.
*
* @param pSource the java source
*/@SuppressWarnings({
"unchecked", "rawtypes"
})
private void load(String pSource) {
jdoc = new JavadocInfo();
if (parser == null) {
parser = ASTUtil.createParser();
}
parser.setSource(pSource.toCharArray());
cuSource = (CompilationUnit) parser.createAST(null);
if (docOrig != null) {
docOrig.set(pSource);
} else {
docOrig = new Document(pSource);
}
//we support only one class per file
List types = cuSource.types();
if (types.size() > 0 && types.get(0) instanceof TypeDeclaration) {
tdClass = (TypeDeclaration) types.get(0);
String sPackage;
//we have to use the current package, if set
if (cuSource.getPackage() != null) {
sPackage = cuSource.getPackage().getName().getFullyQualifiedName() + ".";
} else {
sPackage = "";
}
sClassName = sPackage + tdClass.getName().getFullyQualifiedName();
if (tdClass.getSuperclassType() != null) {
String sSuperName = ((SimpleType)(tdClass.getSuperclassType())).getName().getFullyQualifiedName();
if (sSuperName.indexOf('.') > 0) {
sSuperClass = sSuperName;
} else {
//try to detect the full qualified name of the super class through the imports
List < ImportDeclaration > liImports = cuSource.imports();
boolean bFound = false;
if (liImports != null) {
String sTempName = "." + sSuperName;
String sImport;
for (int i = 0, anz = liImports.size(); !bFound && i < anz; i++) {
sImport = liImports.get(i).getName().getFullyQualifiedName();
if (sImport.endsWith(sTempName)) {
bFound = true;
sSuperClass = liImports.get(i).getName().getFullyQualifiedName();
}
}
}
if (!bFound) {
//we use the current package
sSuperClass = sPackage + sSuperName;
}
}
//cache for optimizing imports in apply
sLoadedSuperClass = sSuperClass;
}
jdoc = new JavadocInfo(tdClass.getJavadoc(), pSource);
} else {
tdClass = null;
}
//currently loaded file can not be changed!
bChanged = false;
}
/**
* Sets the comment for the class.
*
* @param pText the comment
*/
public void setClassComment(String pText) {
bChanged |= !CommonUtil.equals(pText, jdoc.getDescription());
jdoc.setDescription(pText);
}
/**
* Gets the comment for the class.
*
* @return the comment
*/
public String getClassComment() {
return jdoc.getDescription();
}
/**
* Sets the full qualified name of the class.
*
* @param pName the class name
*/
public void setClassName(String pName) {
if (pName == null) {
throw new IllegalArgumentException("It is not allowed to remove the class definition (class name = null)!");
}
bChanged |= !CommonUtil.equals(pName, sClassName);
sClassName = pName;
}
/**
* Gets the full qualified name of the class.
*
* @return the class name
*/
public String getClassName() {
return sClassName;
}
/**
* Sets the full qualified class name of the parent class.
*
* @param pSuperClass the full qualified class name of the parent class
*/
public void setSuperClass(String pSuperClass) {
bChanged |= !CommonUtil.equals(pSuperClass, sSuperClass);
sSuperClass = pSuperClass;
}
/**
* Gets the full qualified class name of the parent class.
*
* @return the class name
*/
public String getSuperClass() {
return sSuperClass;
}
/**
* Replaces the relevant information with the specific values
* and saves the source file.
*
* @throws Exception if the source modification fails
*/@SuppressWarnings("unchecked")
protected void apply() throws Exception {
if (!bChanged) {
return;
}
AST ast = cuSource.getAST();
ASTRewrite arw = ASTRewrite.create(ast);
//----------------------------------------------------
// Change Package, Classname and Constructor
//----------------------------------------------------
String sCurrentPackage;
//set package
String[] sClassInfo = ClassUtil.splitName(sClassName);
if (sClassInfo[0] != null) {
if (cuSource.getPackage() != null) {
arw.set(cuSource.getPackage(), PackageDeclaration.NAME_PROPERTY, ast.newName(sClassInfo[0]), null);
sCurrentPackage = sClassInfo[0];
} else {
PackageDeclaration pdcl = ast.newPackageDeclaration();
pdcl.setName(ast.newName(sClassInfo[0]));
arw.set(cuSource, CompilationUnit.PACKAGE_PROPERTY, pdcl, null);
sCurrentPackage = sClassInfo[0];
}
} else {
arw.set(cuSource, CompilationUnit.PACKAGE_PROPERTY, null, null);
sCurrentPackage = null;
}
if (tdClass == null) {
//new class
tdClass = ast.newTypeDeclaration();
tdClass.setName(ast.newSimpleName(sClassInfo[1]));
ListRewrite lrwImp = arw.getListRewrite(cuSource, CompilationUnit.TYPES_PROPERTY);
lrwImp.insertFirst(tdClass, null);
} else {
//change class name
SimpleName snClass = ast.newSimpleName(sClassInfo[1]);
arw.set(tdClass, TypeDeclaration.NAME_PROPERTY, snClass, null);
//chage constructors
MethodDeclaration[] methods = tdClass.getMethods();
if (methods != null) {
for (MethodDeclaration method: methods) {
if (method.isConstructor()) {
arw.set(method, MethodDeclaration.NAME_PROPERTY, snClass, null);
}
}
}
}
//change Superclass
List < ImportDeclaration > liImports = cuSource.imports();
//remove "old" import
if (sLoadedSuperClass != null) {
if (liImports != null) {
boolean bFound = false;
for (int i = liImports.size() - 1; !bFound && i >= 0; i--) {
if (sLoadedSuperClass.equals(liImports.get(i).getName().getFullyQualifiedName())) {
bFound = true;
arw.remove(liImports.get(i), null);
liImports.remove(i);
}
}
}
}
if (sSuperClass == null) {
arw.set(tdClass, TypeDeclaration.SUPERCLASS_TYPE_PROPERTY, null, null);
} else {
//check new super class import
String[] sSuperClassInfo = ClassUtil.splitName(sSuperClass);
if (sSuperClassInfo[0] != null) {
boolean bFound = false;
if (sCurrentPackage != null) {
bFound = sSuperClassInfo[0].equals(sCurrentPackage);
}
if (!bFound) {
if (liImports != null) {
for (int i = 0, anz = liImports.size(); !bFound && i < anz; i++) {
bFound = sSuperClass.equals(liImports.get(i).getName().getFullyQualifiedName());
}
}
}
//class not already importet -> add import
if (!bFound) {
ImportDeclaration idSuper = ast.newImportDeclaration();
idSuper.setName(ast.newName(sSuperClass));
ListRewrite lrwImp = arw.getListRewrite(cuSource, CompilationUnit.IMPORTS_PROPERTY);
lrwImp.insertLast(idSuper, null);
}
}
arw.set(tdClass, TypeDeclaration.SUPERCLASS_TYPE_PROPERTY, ast.newSimpleType(ast.newName(sSuperClassInfo[1])), null);
}
//----------------------------------------------------
// Change Javadoc of the class
//----------------------------------------------------
if ("".equals(jdoc.toString())) {
//no javadoc
arw.set(tdClass, TypeDeclaration.JAVADOC_PROPERTY, null, null);
} else {
arw.set(tdClass, TypeDeclaration.JAVADOC_PROPERTY, jdoc.createJavadoc(tdClass.getAST()), null);
}
//----------------------------------------------------
// Modify AST
//----------------------------------------------------
TextEdit edit = arw.rewriteAST(docOrig, null);
edit.apply(docOrig);
ASTUtil.applyFormat(docOrig);
//simple reparse (important for the comments)
parser.setSource(docOrig.get().toCharArray());
cuSource = (CompilationUnit) parser.createAST(null);
//----------------------------------------------------
// Modify comments
//----------------------------------------------------
//Class comment after the last bracket
int iCorr = 0;
List < Comment > liComments = cuSource.getCommentList();
if (liComments != null) {
String sOldName = tdClass.getName().getFullyQualifiedName();
String sText;
for (Comment com: liComments) {
sText = docOrig.get(com.getStartPosition() - iCorr, com.getLength());
int iPos = 0;
int iClassLen = sClassInfo[1].length();
int iLenDiff = sOldName.length() - iClassLen;
int iNextPos;
CharacterType ctypePrev;
CharacterType ctypeNext;
while ((iPos = sText.indexOf(sOldName, iPos)) >= 0) {
if (iPos > 0) {
ctypePrev = StringUtil.getCharacterType("" + sText.charAt(iPos - 1));
} else {
ctypePrev = CharacterType.OnlySpecial;
}
iNextPos = iPos + sOldName.length();
if (iNextPos <= sText.length() - 1) {
ctypeNext = StringUtil.getCharacterType("" + sText.charAt(iNextPos));
} else {
ctypeNext = CharacterType.OnlySpecial;
}
//don't replace word parts e.g. ContactsWorkScreen should not be replaced with Contacts
if ((ctypePrev == CharacterType.OnlySpecial || ctypePrev == CharacterType.OnlyWhitespace) && (ctypeNext == CharacterType.OnlySpecial || ctypeNext == CharacterType.OnlyWhitespace)) {
docOrig.replace(com.getStartPosition() + iPos - iCorr, sOldName.length(), sClassInfo[1]);
iCorr += iLenDiff;
iPos += iClassLen;
} else {
iPos = iNextPos;
}
}
}
}
//----------------------------------------------------
// Update
//----------------------------------------------------
load(docOrig.get());
bChanged = false;
}
/**
* Stores the changed source into the given file.
*
* @param pFile the target file for the source
* @throws Exception if an error occurs during saving
*/
public void saveAs(File pFile) throws Exception {
apply();
JavaSource source = JavaSource.get(pFile.getAbsolutePath());
if (source != null) {
synchronized(source) {
FileUtil.save(pFile, docOrig.get().getBytes());
source.reload();
}
} else {
FileUtil.save(pFile, docOrig.get().getBytes());
}
}
/**
* Stores the changed source into the given stream.
*
* @param pStream the target stream for the source
* @throws Exception if an error occurs during saving
*/
public void saveAs(OutputStream pStream) throws Exception {
apply();
pStream.write(docOrig.get().getBytes());
pStream.flush();
}
/**
* Returns the modified source code.
*
* @return the modified source code
* @throws Exception if the source modification fails
*/
protected String getSource() throws Exception {
apply();
return docOrig.get();
}
} // SimpleSourceFile