Rework CPD's LanguageFactory to use ServiceLoader, too

This commit is contained in:
Andreas Dangel
2014-10-11 12:16:28 +02:00
parent 72719bd0c3
commit 464b0bcef8
29 changed files with 95 additions and 97 deletions

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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);

View File

@ -7,6 +7,7 @@ import java.io.FilenameFilter;
import java.util.Properties;
public interface Language {
String getName();
Tokenizer getTokenizer();

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.CpddummyLanguage

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.CPPLanguage

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.CsLanguage

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.FortranLanguage

View File

@ -11,7 +11,7 @@ public class JavaLanguage extends AbstractLanguage {
}
public JavaLanguage(Properties properties) {
super(new JavaTokenizer(), ".java");
super("java", new JavaTokenizer(), ".java");
setProperties(properties);
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.JavaLanguage

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.EcmascriptLanguage

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.JSPLanguage

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.PHPLanguage

View File

@ -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

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.PLSQLLanguage

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
net.sourceforge.pmd.cpd.RubyLanguage