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; import net.sourceforge.pmd.util.filter.Filters;
public abstract class AbstractLanguage implements Language { public abstract class AbstractLanguage implements Language {
private final String name;
private final Tokenizer tokenizer; private final Tokenizer tokenizer;
private final FilenameFilter fileFilter; private final FilenameFilter fileFilter;
public AbstractLanguage(Tokenizer tokenizer, String... extensions) { public AbstractLanguage(String name, Tokenizer tokenizer, String... extensions) {
this.name = name;
this.tokenizer = tokenizer; this.tokenizer = tokenizer;
fileFilter = Filters.toFilenameFilter(Filters.getFileExtensionOrDirectoryFilter(extensions)); fileFilter = Filters.toFilenameFilter(Filters.getFileExtensionOrDirectoryFilter(extensions));
} }
@ -28,4 +30,8 @@ public abstract class AbstractLanguage implements Language {
public void setProperties(Properties properties) { public void setProperties(Properties properties) {
// needs to be implemented by subclasses. // 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 class AnyLanguage extends AbstractLanguage {
public AnyLanguage(String... extension) { 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)) { if (languageString == null || "".equals(languageString)) {
languageString = DEFAULT_LANGUAGE; 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) { public static Language getLanguageFromString(String languageString) {
return new LanguageFactory().createLanguage(languageString); return LanguageFactory.createLanguage(languageString);
} }
public static void setSystemProperties(CPDConfiguration configuration) { public static void setSystemProperties(CPDConfiguration configuration) {

View File

@ -96,7 +96,7 @@ public class CPDTask extends Task {
if (ignoreAnnotations) { if (ignoreAnnotations) {
p.setProperty(Tokenizer.IGNORE_ANNOTATIONS, "true"); p.setProperty(Tokenizer.IGNORE_ANNOTATIONS, "true");
} }
return new LanguageFactory().createLanguage(language, p); return LanguageFactory.createLanguage(language, p);
} }
private void report(CPD cpd) throws ReportException { private void report(CPD cpd) throws ReportException {

View File

@ -81,7 +81,7 @@ public class GUI implements CPDListener {
}; };
private static abstract class LanguageConfig { 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 canIgnoreIdentifiers() { return false; }
public boolean canIgnoreLiterals() { return false; } public boolean canIgnoreLiterals() { return false; }
public boolean canIgnoreAnnotations() { return false; } public boolean canIgnoreAnnotations() { return false; }
@ -90,31 +90,31 @@ public class GUI implements CPDListener {
private static final Object[][] LANGUAGE_SETS = new Object[][] { private static final Object[][] LANGUAGE_SETS = new Object[][] {
{"Java", new LanguageConfig() { {"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 canIgnoreIdentifiers() { return true; }
public boolean canIgnoreLiterals() { return true; } public boolean canIgnoreLiterals() { return true; }
public boolean canIgnoreAnnotations() { return true; } public boolean canIgnoreAnnotations() { return true; }
public String[] extensions() { return new String[] {".java", ".class" }; }; } }, public String[] extensions() { return new String[] {".java", ".class" }; }; } },
{"JSP", new LanguageConfig() { {"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" }; }; } }, public String[] extensions() { return new String[] {".jsp" }; }; } },
{"C++", new LanguageConfig() { {"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" }; }; } }, public String[] extensions() { return new String[] {".cpp", ".c" }; }; } },
{"Ruby", new LanguageConfig() { {"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" }; }; } }, public String[] extensions() { return new String[] {".rb" }; }; } },
{"Fortran", new LanguageConfig() { {"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" }; }; } }, public String[] extensions() { return new String[] {".for", ".f", ".f66", ".f77", ".f90" }; }; } },
{"PHP", new LanguageConfig() { {"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" }; }; } }, public String[] extensions() { return new String[] {".php" }; }; } },
{"C#", new LanguageConfig() { {"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" }; }; } }, public String[] extensions() { return new String[] {".cs" }; }; } },
{"PLSQL", new LanguageConfig() { {"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" public String[] extensions() { return new String[] {".sql"
,".trg" ,".trg"
,".prc",".fnc" ,".prc",".fnc"
@ -125,10 +125,10 @@ public class GUI implements CPDListener {
,".tps",".tpb" ,".tps",".tpb"
}; }; } }, }; }; } },
{"Ecmascript", new LanguageConfig() { {"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" }; }; } }, public String[] extensions() { return new String[] {".js" }; }; } },
{"by extension...", new LanguageConfig() { {"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[] {"" }; }; } }, public String[] extensions() { return new String[] {"" }; }; } },
}; };
@ -589,7 +589,7 @@ public class GUI implements CPDListener {
p.setProperty(LanguageFactory.EXTENSION, extensionField.getText()); p.setProperty(LanguageFactory.EXTENSION, extensionField.getText());
LanguageConfig conf = languageConfigFor((String)languageBox.getSelectedItem()); LanguageConfig conf = languageConfigFor((String)languageBox.getSelectedItem());
Language language = conf.languageFor(new LanguageFactory(), p); Language language = conf.languageFor(p);
config.setLanguage(language); config.setLanguage(language);
CPDConfiguration.setSystemProperties(config); CPDConfiguration.setSystemProperties(config);

View File

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

View File

@ -3,56 +3,52 @@
*/ */
package net.sourceforge.pmd.cpd; package net.sourceforge.pmd.cpd;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.ServiceLoader;
public class LanguageFactory { public class LanguageFactory {
/* public static final String EXTENSION = "extension";
* TODO derive and provide this at runtime instead, used by outside IDEs public static final String BY_EXTENSION = "by_extension";
* 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"; private static LanguageFactory instance = new LanguageFactory();
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) { 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()); return createLanguage(language, new Properties());
} }
public Language createLanguage(String language, Properties properties) public static Language createLanguage(String language, Properties properties)
{ {
language = this.languageAliases(language); Language implementation;
// First, we look for a parser following this syntax 'RubyLanguage' if (BY_EXTENSION.equals(language)) {
Language implementation; implementation = instance.getLanguageByExtension(properties.getProperty(EXTENSION));
try { } else {
implementation = this.dynamicLanguageImplementationLoad(this.languageConventionSyntax(language)); implementation = instance.languages.get(instance.languageAliases(language).toLowerCase());
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 )
{
// No proper implementation
// FIXME: We should log a warning, shouldn't we ?
return new AnyLanguage(language);
}
}
return implementation;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} }
return null; if (implementation == null) {
// No proper implementation
// FIXME: We should log a warning, shouldn't we ?
implementation = new AnyLanguage(language);
}
implementation.setProperties(properties);
return implementation;
} }
private String languageAliases(String language) private String languageAliases(String language)
@ -63,32 +59,18 @@ public class LanguageFactory {
} }
return language; return language;
} }
private Language getLanguageByExtension(String extension) {
Language result = null;
File dir = new File(".");
String filename = "file." + extension;
private Language dynamicLanguageImplementationLoad(String language) throws InstantiationException, IllegalAccessException for (Language language : languages.values()) {
{ if (language.getFileFilter().accept(dir, filename)) {
try { result = language;
return (Language) this.getClass().getClassLoader().loadClass( break;
PACKAGE + language + SUFFIX).newInstance(); }
} catch (ClassNotFoundException e) { }
// No class found, returning default implementation return result;
// 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;
}
}
/*
* 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();
}
} }

View File

@ -7,9 +7,9 @@ package net.sourceforge.pmd.cpd;
* Sample language for testing LanguageFactory. * Sample language for testing LanguageFactory.
* *
*/ */
public class CpddummyLanguage extends AnyLanguage { public class CpddummyLanguage extends AbstractLanguage {
public CpddummyLanguage() { public CpddummyLanguage() {
super("dummy"); super("Cpddummy", new AnyTokenizer(), "dummy");
} }
} }

View File

@ -10,8 +10,7 @@ public class LanguageFactoryTest {
@Test @Test
public void testSimple() { public void testSimple() {
LanguageFactory f = new LanguageFactory(); assertTrue(LanguageFactory.createLanguage("Cpddummy") instanceof CpddummyLanguage);
assertTrue(f.createLanguage("Cpddummy") instanceof CpddummyLanguage); assertTrue(LanguageFactory.createLanguage("not_existing_language") instanceof AnyLanguage);
assertTrue(f.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 class CPPLanguage extends AbstractLanguage {
public CPPLanguage() { 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 class CsLanguage extends AbstractLanguage {
public CsLanguage() { 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 class FortranLanguage extends AbstractLanguage {
public FortranLanguage() { 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) { public JavaLanguage(Properties properties) {
super(new JavaTokenizer(), ".java"); super("java", new JavaTokenizer(), ".java");
setProperties(properties); 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 class EcmascriptLanguage extends AbstractLanguage {
public EcmascriptLanguage() { 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 class JSPLanguage extends AbstractLanguage {
public JSPLanguage() { 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 class PHPLanguage extends AbstractLanguage {
public PHPLanguage() { 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 class PLSQLLanguage extends AbstractLanguage {
public PLSQLLanguage() { public PLSQLLanguage() {
super(new PLSQLTokenizer() super("plsql", new PLSQLTokenizer()
,".sql" ,".sql"
,".trg" //Triggers ,".trg" //Triggers
,".prc",".fnc" // Standalone Procedures and Functions ,".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 class RubyLanguage extends AbstractLanguage {
public RubyLanguage() { 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