Rework CPD's LanguageFactory to use ServiceLoader, too
This commit is contained in:
@ -9,10 +9,12 @@ import java.util.Properties;
|
||||
import net.sourceforge.pmd.util.filter.Filters;
|
||||
|
||||
public abstract class AbstractLanguage implements Language {
|
||||
private final String name;
|
||||
private final Tokenizer tokenizer;
|
||||
private final FilenameFilter fileFilter;
|
||||
|
||||
public AbstractLanguage(Tokenizer tokenizer, String... extensions) {
|
||||
public AbstractLanguage(String name, Tokenizer tokenizer, String... extensions) {
|
||||
this.name = name;
|
||||
this.tokenizer = tokenizer;
|
||||
fileFilter = Filters.toFilenameFilter(Filters.getFileExtensionOrDirectoryFilter(extensions));
|
||||
}
|
||||
@ -28,4 +30,8 @@ public abstract class AbstractLanguage implements Language {
|
||||
public void setProperties(Properties properties) {
|
||||
// needs to be implemented by subclasses.
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ package net.sourceforge.pmd.cpd;
|
||||
|
||||
public class AnyLanguage extends AbstractLanguage {
|
||||
public AnyLanguage(String... extension) {
|
||||
super(new AnyTokenizer(), extension);
|
||||
super("any", new AnyTokenizer(), extension);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public class CPDConfiguration extends AbstractConfiguration {
|
||||
if (languageString == null || "".equals(languageString)) {
|
||||
languageString = DEFAULT_LANGUAGE;
|
||||
}
|
||||
return new LanguageFactory().createLanguage(languageString);
|
||||
return LanguageFactory.createLanguage(languageString);
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ public class CPDConfiguration extends AbstractConfiguration {
|
||||
}
|
||||
|
||||
public static Language getLanguageFromString(String languageString) {
|
||||
return new LanguageFactory().createLanguage(languageString);
|
||||
return LanguageFactory.createLanguage(languageString);
|
||||
}
|
||||
|
||||
public static void setSystemProperties(CPDConfiguration configuration) {
|
||||
|
@ -96,7 +96,7 @@ public class CPDTask extends Task {
|
||||
if (ignoreAnnotations) {
|
||||
p.setProperty(Tokenizer.IGNORE_ANNOTATIONS, "true");
|
||||
}
|
||||
return new LanguageFactory().createLanguage(language, p);
|
||||
return LanguageFactory.createLanguage(language, p);
|
||||
}
|
||||
|
||||
private void report(CPD cpd) throws ReportException {
|
||||
|
@ -81,7 +81,7 @@ public class GUI implements CPDListener {
|
||||
};
|
||||
|
||||
private static abstract class LanguageConfig {
|
||||
public abstract Language languageFor(LanguageFactory lf, Properties p);
|
||||
public abstract Language languageFor(Properties p);
|
||||
public boolean canIgnoreIdentifiers() { return false; }
|
||||
public boolean canIgnoreLiterals() { return false; }
|
||||
public boolean canIgnoreAnnotations() { return false; }
|
||||
@ -90,31 +90,31 @@ public class GUI implements CPDListener {
|
||||
|
||||
private static final Object[][] LANGUAGE_SETS = new Object[][] {
|
||||
{"Java", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("java"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("java", p); }
|
||||
public boolean canIgnoreIdentifiers() { return true; }
|
||||
public boolean canIgnoreLiterals() { return true; }
|
||||
public boolean canIgnoreAnnotations() { return true; }
|
||||
public String[] extensions() { return new String[] {".java", ".class" }; }; } },
|
||||
{"JSP", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("jsp"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("jsp", p); }
|
||||
public String[] extensions() { return new String[] {".jsp" }; }; } },
|
||||
{"C++", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("cpp"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("cpp", p); }
|
||||
public String[] extensions() { return new String[] {".cpp", ".c" }; }; } },
|
||||
{"Ruby", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("ruby"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("ruby", p); }
|
||||
public String[] extensions() { return new String[] {".rb" }; }; } },
|
||||
{"Fortran", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("fortran"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("fortran", p); }
|
||||
public String[] extensions() { return new String[] {".for", ".f", ".f66", ".f77", ".f90" }; }; } },
|
||||
{"PHP", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("php"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("php", p); }
|
||||
public String[] extensions() { return new String[] {".php" }; }; } },
|
||||
{"C#", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("cs"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("cs", p); }
|
||||
public String[] extensions() { return new String[] {".cs" }; }; } },
|
||||
{"PLSQL", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("plsql"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("plsql", p); }
|
||||
public String[] extensions() { return new String[] {".sql"
|
||||
,".trg"
|
||||
,".prc",".fnc"
|
||||
@ -125,10 +125,10 @@ public class GUI implements CPDListener {
|
||||
,".tps",".tpb"
|
||||
}; }; } },
|
||||
{"Ecmascript", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage("js"); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage("js", p); }
|
||||
public String[] extensions() { return new String[] {".js" }; }; } },
|
||||
{"by extension...", new LanguageConfig() {
|
||||
public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.BY_EXTENSION, p); }
|
||||
public Language languageFor(Properties p) { return LanguageFactory.createLanguage(LanguageFactory.BY_EXTENSION, p); }
|
||||
public String[] extensions() { return new String[] {"" }; }; } },
|
||||
};
|
||||
|
||||
@ -589,7 +589,7 @@ public class GUI implements CPDListener {
|
||||
p.setProperty(LanguageFactory.EXTENSION, extensionField.getText());
|
||||
|
||||
LanguageConfig conf = languageConfigFor((String)languageBox.getSelectedItem());
|
||||
Language language = conf.languageFor(new LanguageFactory(), p);
|
||||
Language language = conf.languageFor(p);
|
||||
config.setLanguage(language);
|
||||
|
||||
CPDConfiguration.setSystemProperties(config);
|
||||
|
@ -7,6 +7,7 @@ import java.io.FilenameFilter;
|
||||
import java.util.Properties;
|
||||
|
||||
public interface Language {
|
||||
String getName();
|
||||
|
||||
Tokenizer getTokenizer();
|
||||
|
||||
|
@ -3,56 +3,52 @@
|
||||
*/
|
||||
package net.sourceforge.pmd.cpd;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public class LanguageFactory {
|
||||
|
||||
/*
|
||||
* TODO derive and provide this at runtime instead, used by outside IDEs
|
||||
* FIXME Can't we do something cleaner and
|
||||
* more dynamic ? Maybe externalise to a properties files that will
|
||||
* be generated when building pmd ? This will not have to add manually
|
||||
* new language here ?
|
||||
*/
|
||||
public static String[] supportedLanguages =
|
||||
new String[]{"java", "jsp", "cpp", "c", "php", "ruby", "fortran", "ecmascript", "cs", "plsql" };
|
||||
|
||||
private static final String SUFFIX = "Language";
|
||||
public static final String EXTENSION = "extension";
|
||||
public static final String BY_EXTENSION = "by_extension";
|
||||
private static final String PACKAGE = "net.sourceforge.pmd.cpd.";
|
||||
|
||||
public Language createLanguage(String language) {
|
||||
private static LanguageFactory instance = new LanguageFactory();
|
||||
|
||||
public static String[] supportedLanguages;
|
||||
static {
|
||||
supportedLanguages = instance.languages.keySet().toArray(new String[instance.languages.size()]);
|
||||
}
|
||||
|
||||
private Map<String, Language> languages = new HashMap<String, Language>();
|
||||
|
||||
private LanguageFactory() {
|
||||
ServiceLoader<Language> languageLoader = ServiceLoader.load(Language.class);
|
||||
for (Language language : languageLoader) {
|
||||
languages.put(language.getName().toLowerCase(), language);
|
||||
}
|
||||
}
|
||||
|
||||
public static Language createLanguage(String language) {
|
||||
return createLanguage(language, new Properties());
|
||||
}
|
||||
|
||||
public Language createLanguage(String language, Properties properties)
|
||||
public static Language createLanguage(String language, Properties properties)
|
||||
{
|
||||
language = this.languageAliases(language);
|
||||
// First, we look for a parser following this syntax 'RubyLanguage'
|
||||
Language implementation;
|
||||
try {
|
||||
implementation = this.dynamicLanguageImplementationLoad(this.languageConventionSyntax(language));
|
||||
if ( implementation == null )
|
||||
{
|
||||
// if it failed, we look for a parser following this syntax 'CPPLanguage'
|
||||
implementation = this.dynamicLanguageImplementationLoad(language.toUpperCase());
|
||||
//TODO: Should we try to break the coupling with PACKAGE by try to load the class
|
||||
// based on her sole name ?
|
||||
if ( implementation == null )
|
||||
{
|
||||
if (BY_EXTENSION.equals(language)) {
|
||||
implementation = instance.getLanguageByExtension(properties.getProperty(EXTENSION));
|
||||
} else {
|
||||
implementation = instance.languages.get(instance.languageAliases(language).toLowerCase());
|
||||
}
|
||||
if (implementation == null) {
|
||||
// No proper implementation
|
||||
// FIXME: We should log a warning, shouldn't we ?
|
||||
return new AnyLanguage(language);
|
||||
}
|
||||
implementation = new AnyLanguage(language);
|
||||
}
|
||||
implementation.setProperties(properties);
|
||||
return implementation;
|
||||
} catch (InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String languageAliases(String language)
|
||||
@ -64,31 +60,17 @@ public class LanguageFactory {
|
||||
return language;
|
||||
}
|
||||
|
||||
private Language dynamicLanguageImplementationLoad(String language) throws InstantiationException, IllegalAccessException
|
||||
{
|
||||
try {
|
||||
return (Language) this.getClass().getClassLoader().loadClass(
|
||||
PACKAGE + language + SUFFIX).newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
// No class found, returning default implementation
|
||||
// FIXME: There should be somekind of log of this
|
||||
return null;
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// Windows is case insensitive, so it may find the file, even though
|
||||
// the name has a different case. Since Java is case sensitive, it
|
||||
// will not accept the classname inside the file that was found and
|
||||
// will throw a NoClassDefFoundError
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private Language getLanguageByExtension(String extension) {
|
||||
Language result = null;
|
||||
File dir = new File(".");
|
||||
String filename = "file." + extension;
|
||||
|
||||
/*
|
||||
* This method does simply this:
|
||||
* ruby -> Ruby
|
||||
* fortran -> Fortran
|
||||
* ...
|
||||
*/
|
||||
private String languageConventionSyntax(String language) {
|
||||
return Character.toUpperCase(language.charAt(0)) + language.substring(1, language.length()).toLowerCase();
|
||||
for (Language language : languages.values()) {
|
||||
if (language.getFileFilter().accept(dir, filename)) {
|
||||
result = language;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ package net.sourceforge.pmd.cpd;
|
||||
* Sample language for testing LanguageFactory.
|
||||
*
|
||||
*/
|
||||
public class CpddummyLanguage extends AnyLanguage {
|
||||
public class CpddummyLanguage extends AbstractLanguage {
|
||||
|
||||
public CpddummyLanguage() {
|
||||
super("dummy");
|
||||
super("Cpddummy", new AnyTokenizer(), "dummy");
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,7 @@ public class LanguageFactoryTest {
|
||||
|
||||
@Test
|
||||
public void testSimple() {
|
||||
LanguageFactory f = new LanguageFactory();
|
||||
assertTrue(f.createLanguage("Cpddummy") instanceof CpddummyLanguage);
|
||||
assertTrue(f.createLanguage("not_existing_language") instanceof AnyLanguage);
|
||||
assertTrue(LanguageFactory.createLanguage("Cpddummy") instanceof CpddummyLanguage);
|
||||
assertTrue(LanguageFactory.createLanguage("not_existing_language") instanceof AnyLanguage);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.CpddummyLanguage
|
@ -5,6 +5,6 @@ package net.sourceforge.pmd.cpd;
|
||||
|
||||
public class CPPLanguage extends AbstractLanguage {
|
||||
public CPPLanguage() {
|
||||
super(new CPPTokenizer(), ".h", ".hpp", ".hxx",".c", ".cpp", ".cxx", ".cc", ".C");
|
||||
super("cpp", new CPPTokenizer(), ".h", ".hpp", ".hxx",".c", ".cpp", ".cxx", ".cc", ".C");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.CPPLanguage
|
@ -5,6 +5,6 @@ package net.sourceforge.pmd.cpd;
|
||||
|
||||
public class CsLanguage extends AbstractLanguage {
|
||||
public CsLanguage() {
|
||||
super(new CsTokenizer(), ".cs");
|
||||
super("cs", new CsTokenizer(), ".cs");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.CsLanguage
|
@ -9,6 +9,6 @@ package net.sourceforge.pmd.cpd;
|
||||
*/
|
||||
public class FortranLanguage extends AbstractLanguage {
|
||||
public FortranLanguage() {
|
||||
super(new FortranTokenizer(), ".for", ".f", ".f66", ".f77", ".f90");
|
||||
super("fortran", new FortranTokenizer(), ".for", ".f", ".f66", ".f77", ".f90");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.FortranLanguage
|
@ -11,7 +11,7 @@ public class JavaLanguage extends AbstractLanguage {
|
||||
}
|
||||
|
||||
public JavaLanguage(Properties properties) {
|
||||
super(new JavaTokenizer(), ".java");
|
||||
super("java", new JavaTokenizer(), ".java");
|
||||
setProperties(properties);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.JavaLanguage
|
@ -9,6 +9,6 @@ package net.sourceforge.pmd.cpd;
|
||||
*/
|
||||
public class EcmascriptLanguage extends AbstractLanguage {
|
||||
public EcmascriptLanguage() {
|
||||
super(new EcmascriptTokenizer(), ".js");
|
||||
super("ecmascript", new EcmascriptTokenizer(), ".js");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.EcmascriptLanguage
|
@ -5,6 +5,6 @@ package net.sourceforge.pmd.cpd;
|
||||
|
||||
public class JSPLanguage extends AbstractLanguage {
|
||||
public JSPLanguage() {
|
||||
super(new JSPTokenizer(), ".jsp", ".jspx");
|
||||
super("jsp", new JSPTokenizer(), ".jsp", ".jspx");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.JSPLanguage
|
@ -5,6 +5,6 @@ package net.sourceforge.pmd.cpd;
|
||||
|
||||
public class PHPLanguage extends AbstractLanguage {
|
||||
public PHPLanguage() {
|
||||
super(new PHPTokenizer(), ".php", ".class");
|
||||
super("php", new PHPTokenizer(), ".php", ".class");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.PHPLanguage
|
@ -11,7 +11,7 @@ import java.util.Properties;
|
||||
*/
|
||||
public class PLSQLLanguage extends AbstractLanguage {
|
||||
public PLSQLLanguage() {
|
||||
super(new PLSQLTokenizer()
|
||||
super("plsql", new PLSQLTokenizer()
|
||||
,".sql"
|
||||
,".trg" //Triggers
|
||||
,".prc",".fnc" // Standalone Procedures and Functions
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.PLSQLLanguage
|
@ -9,6 +9,6 @@ package net.sourceforge.pmd.cpd;
|
||||
*/
|
||||
public class RubyLanguage extends AbstractLanguage {
|
||||
public RubyLanguage() {
|
||||
super(new RubyTokenizer(), ".rb", ".cgi", ".class");
|
||||
super("ruby", new RubyTokenizer(), ".rb", ".cgi", ".class");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
net.sourceforge.pmd.cpd.RubyLanguage
|
Reference in New Issue
Block a user