package net.jangaroo.exml.mojo;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import static org.apache.commons.io.FileUtils.listFiles;
import static org.apache.commons.lang.CharEncoding.UTF_8;
/**
* A Mojo to make all exml target class file with the same name as the exml file explicitly as baseClass of the exml class
* @goal exml-target-to-base
* @phase generate-sources
* @requiresDependencyResolution
* @threadSafe
*/
public class ExmlTargetToBaseClassMojo extends AbstractExmlMojo {
public static final String PRIVATE_PREFIX = "private";
public static final String PROTECTED_PREFIX = "protected";
public static final String INTERNAL_PREFIX = "internal";
public static final String PUBLIC_PREFIX = "public";
public static final String STATIC_PREFIX = "static";
public static final String FUNCTION_LOWER_CASE = "function";
public static final String CONST = "const";
public static final String VAR = "var";
public static final String FUNCTION_UPPER_CASE = "Function";
public static void main(String[] args) {
if (args.length > 0) {
String sourceDirPath = args[0];
File sourceDir = new File(sourceDirPath);
new ExmlTargetToBaseClassMojo().execute(sourceDir);
}
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (!isExmlProject()) {
getLog().info("not an EXML project, skipping base class conversion");
return;
}
execute(getSourceDirectory());
execute(getTestSourceDirectory());
}
private void execute(File sourceDir) {
if (sourceDir != null && sourceDir.exists()) {
int fixedExmls = 0;
for (final File exmlFile : listFiles(sourceDir, new String[]{"exml"}, true)) {
File targetFile = findTargetFileWithSameName(exmlFile);
if (targetFile == null) {
continue; //no target file with same name found
}
File baseFile = renameTargetToBase(targetFile);
if (baseFile == null) {
continue; //rename failed
}
try {
fixBaseClassName(exmlFile, baseFile);
addBaseClassDeclaration(exmlFile, baseFile);
logTargetClassesWithStatics(exmlFile, baseFile);
fixedExmls++;
} catch (IOException e) {
getLog().error("Fixing of class name failed", e);
}
}
getLog().info("Number of fixed exml/AS pair: " + fixedExmls);
}
}
//fix the class name in the base class file itself
private void fixBaseClassName(File exmlFile, File baseFile) throws IOException {
String baseClassContent = FileUtils.readFileToString(baseFile, UTF_8);
//fix the class declaration
String oldClassDeclarationPattern = "class\\s+" + getName(exmlFile);
baseClassContent = baseClassContent.replaceAll(oldClassDeclarationPattern, "class " + getName(baseFile));
//fix the constructor
String oldConstructorPattern = "public\\s+function\\s+" + getName(exmlFile);
baseClassContent = baseClassContent.replaceAll(oldConstructorPattern, "public function " + getName(baseFile));
FileUtils.write(baseFile, baseClassContent, UTF_8);
getLog().info("baseClass name fixed in : " + baseFile.getAbsolutePath());
}
//add the baseClass declaration in the exml
private void addBaseClassDeclaration(File exmlFile, File baseFile) throws IOException {
String exmlContent = FileUtils.readFileToString(exmlFile, UTF_8);
int i = exmlContent.indexOf("<exml:");
int j = exmlContent.indexOf(">", i);
String exmlBefore = exmlContent.substring(0, i);
String exmlDeclaration = exmlContent.substring(i, j + 1);
//remove the baseClass declaration (unnecessary but there are such codes, e.g. PremularBase.exml)
if (exmlDeclaration.indexOf("baseClass") > 0) {
exmlDeclaration = exmlDeclaration.replaceAll("baseClass[\\s\\S]*?\"[\\s\\S]*?\"[\r\n\\s]*", "");
}
String exmlAfter = exmlContent.substring(j + 1);
if (exmlDeclaration.endsWith("/>")) {
exmlDeclaration = exmlDeclaration.substring(0, exmlDeclaration.length() - 2);
exmlDeclaration += "\r\nbaseClass=\"" + getName(baseFile) + "\"/>";
} else {
exmlDeclaration = exmlDeclaration.substring(0, exmlDeclaration.length() - 1);
exmlDeclaration += "\r\nbaseClass=\"" + getName(baseFile) + "\">";
}
exmlContent = exmlBefore + exmlDeclaration + exmlAfter;
FileUtils.write(exmlFile, exmlContent, UTF_8);
getLog().info("baseClass declaration added to: " + exmlFile.getAbsolutePath());
}
private void logTargetClassesWithStatics(File exmlFile, File baseFile) throws IOException {
List<String> baseClassLines = FileUtils.readLines(baseFile, UTF_8);
List<String> staticList = new ArrayList<String>();
for (String line : baseClassLines) {
String staticName = null;
String staticType = null;
List<String> tokens = getRelevantTokens(line);
if (tokens.isEmpty()) {
continue;
}
if (tokens.get(0).equals(FUNCTION_LOWER_CASE)) {
staticName = tokens.get(1).split("\\(")[0];
staticType = FUNCTION_UPPER_CASE;
} else if (tokens.get(0).equals(CONST) || tokens.get(0).equals(VAR)) {
String[] constNameAndTypeTokens = tokens.get(1).split(":");
staticName = constNameAndTypeTokens[0];
staticType = constNameAndTypeTokens.length > 1 ? constNameAndTypeTokens[1] : "Object";
//handle "public static const bla:blub=" (without space between type and "=")
staticType = staticType.split("=")[0];
//handle "public static var bla:blub; (without value)
staticType = staticType.split(";")[0];
}
if (staticName != null) {
staticList.add(staticType + " " + staticName);
}
}
if (!staticList.isEmpty()) {
getLog().info("The renamed target file " + baseFile.getAbsolutePath() + " has static members: " + staticList);
}
}
/**
* get all strings after "function" or "const" including "function" or "const"
* empty list if the visibility is "private"
*
* @param line the given line
* @return empty list if the visibility is "private"
*/
private List<String> getRelevantTokens(String line) {
String cleanLine = line.trim().replaceAll("\\s+", " ");
//what if "public static const bla : blub" (space before ":" and after)?
cleanLine = cleanLine.replaceAll(" :", ":");
cleanLine = cleanLine.replaceAll(" :", ":");
List<String> lineTokens = new LinkedList<String>(Arrays.asList(cleanLine.split(" ")));
if (lineTokens.contains(PRIVATE_PREFIX) || !lineTokens.contains(STATIC_PREFIX)) {
return Collections.emptyList();
}
lineTokens.remove(PUBLIC_PREFIX);
lineTokens.remove(PROTECTED_PREFIX);
lineTokens.remove(INTERNAL_PREFIX);
lineTokens.remove(STATIC_PREFIX);
return lineTokens;
}
private File findTargetFileWithSameName(final File exmlFile) {
File[] files = exmlFile.getParentFile().listFiles(new FilenameFilter() {
//check if there is a as file in the same directory as the exml file
String targetFileName = getName(exmlFile) + ".as";
@Override
public boolean accept(File dir, String name) {
return targetFileName.equals(name);
}
});
if (files.length == 1) {
getLog().info(exmlFile.getName() + " and " + files[0].getName() + " found in " + exmlFile.getParentFile().getAbsolutePath());
return files[0];
} else {
if (files.length > 1) {
getLog().warn("There is more than one target AS file with same name in the directory like " + exmlFile.getAbsolutePath());
}
return null;
}
}
private File renameTargetToBase(File targetFile) {
File baseClassFile = new File(targetFile.getAbsolutePath().replace(".as", "Base.as"));
if (targetFile.renameTo(baseClassFile)) {
getLog().info("Target file " + targetFile.getName() + " is renamed to " + baseClassFile.getName() + " in " +
baseClassFile.getParent());
return baseClassFile;
} else {
getLog().error("Renaming to " + baseClassFile.getAbsolutePath() + " failed");
return null;
}
}
private String getName(File file) {
String[] nameAndExtension = file.getName().split("\\.");
if (nameAndExtension.length != 2) {
getLog().warn("Cannot compute the name of the file: " + file.getAbsolutePath());
return null;
}
return nameAndExtension[0];
}
}