Language.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang;
005 
006 import java.util.ArrayList;
007 import java.util.Arrays;
008 import java.util.Collections;
009 import java.util.List;
010 
011 import net.sourceforge.pmd.lang.ecmascript.rule.EcmascriptRuleChainVisitor;
012 import net.sourceforge.pmd.lang.java.rule.JavaRuleChainVisitor;
013 import net.sourceforge.pmd.lang.jsp.rule.JspRuleChainVisitor;
014 import net.sourceforge.pmd.lang.rule.RuleChainVisitor;
015 import net.sourceforge.pmd.lang.xml.rule.XmlRuleChainVisitor;
016 
017 /**
018  * This is an enumeration of the Languages of which PMD is aware.  The primary
019  * use of a Language is for Rules, but they are also used by utilities such as
020  * CPD.
021  <p>
022  * The following are key components of a Language in PMD:
023  <ul>
024  *   <li>Name - Full name of the Language</li>
025  *   <li>Short name - The common short form of the Language</li>
026  *   <li>Terse name - The shortest and simplest possible form of the Language
027  *     name, generally used for Rule configuration</li>
028  *   <li>Extensions - File extensions associated with the Language</li>
029  *   <li>Rule Chain Visitor - The RuleChainVisitor implementation used for this
030  *     Language</li>
031  *   <li>Versions - The LanguageVersions associated with the Language</li>
032  </ul>
033  *
034  @see LanguageVersion
035  @see LanguageVersionDiscoverer
036  */
037 public enum Language {
038 
039     //ANY("Any", null, null, null, (String)null),
040     //UNKNOWN("Unknown", null, "unknown", null, (String)null),
041     CPP("C++", null, "cpp", null, "h""c""cpp""cxx""cc""C"),
042     FORTRAN("Fortran", null, "fortran", null, "for"),
043     ECMASCRIPT("Ecmascript", null, "ecmascript", EcmascriptRuleChainVisitor.class, "js"),
044     JAVA("Java", null, "java", JavaRuleChainVisitor.class, "java"),
045     JSP("Java Server Pages""JSP""jsp", JspRuleChainVisitor.class, "jsp"),
046     PHP("PHP: Hypertext Preprocessor""PHP""php", null, "php""class"),
047     RUBY("Ruby", null, "ruby", null, "rb""cgi""class"),
048     XML("XML", null, "xml", XmlRuleChainVisitor.class, "xml");
049 
050     private final String name;
051     private final String shortName;
052     private final String terseName;
053     private final List<String> extensions;
054     private final Class<?> ruleChainVisitorClass;
055     private final List<LanguageVersion> versions;
056 
057     /**
058      * Language constructor.
059      
060      @param name The name of this Language.  Must not be <code>null</code>.
061      @param shortName The short name of this Language, if <code>null</code> the
062      * name will be used at the short name.
063      @param terseName The terse name of this Language.
064      * Must not be <code>null</code>.
065      @param ruleChainVisitorClass The RuleChainVisitor implementation class.
066      * May be <code>null</code>.
067      @param extensions An array of extensions for this Language.
068      * May be <code>null</code>.
069      */
070     private Language(String name, String shortName, String terseName, Class<?> ruleChainVisitorClass,
071       String... extensions) {
072   if (name == null) {
073       throw new IllegalArgumentException("Name must not be null.");
074   }
075   if (terseName == null) {
076       throw new IllegalArgumentException("Terse name must not be null.");
077   }
078   this.name = name;
079   this.shortName = shortName != null ? shortName : name;
080   this.terseName = terseName;
081   this.ruleChainVisitorClass = ruleChainVisitorClass;
082   this.extensions = Collections.unmodifiableList(Arrays.asList(extensions));
083   this.versions = new ArrayList<LanguageVersion>();
084 
085   // Sanity check: RuleChainVisitor is actually so, and has no arg-constructor?
086   if (ruleChainVisitorClass != null) {
087       try {
088     Object obj = ruleChainVisitorClass.newInstance();
089     if (!(obj instanceof RuleChainVisitor)) {
090         throw new IllegalStateException("RuleChainVisitor class <" + ruleChainVisitorClass.getName()
091           "> does not implement the RuleChainVisitor interface!");
092     }
093       catch (InstantiationException e) {
094     throw new IllegalStateException("Unable to invoke no-arg constructor for RuleChainVisitor class <"
095       + ruleChainVisitorClass.getName() ">!");
096       catch (IllegalAccessException e) {
097     throw new IllegalStateException("Unable to invoke no-arg constructor for RuleChainVisitor class <"
098       + ruleChainVisitorClass.getName() ">!");
099       }
100   }
101     }
102 
103     /**
104      * Get the full name of this Language.  This is generally the name of this
105      * Language without the use of acronyms.
106      @return The full name of this Language.
107      */
108     public String getName() {
109   return name;
110     }
111 
112     /**
113      * Get the short name of this Language.  This is the commonly used short
114      * form of this Language's name, perhaps an acronym.
115      @return The short name of this Language.
116      */
117     public String getShortName() {
118   return shortName;
119     }
120 
121     /**
122      * Get the terse name of this Language.  This is used for Rule configuration.
123      @return The terse name of this Language.
124      */
125     public String getTerseName() {
126   return terseName;
127     }
128 
129     /**
130      * Get the list of file extensions associated with this Language. 
131      @return List of file extensions.
132      */
133     public List<String> getExtensions() {
134   return extensions;
135     }
136 
137     /**
138      * Returns whether the given Language handles the given file extension.
139      * The comparison is done ignoring case.
140      @param extension A file extension.
141      @return <code>true</code> if this Language handles this extension, <code>false</code> otherwise.
142      */
143     public boolean hasExtension(String extension) {
144   if (extension != null) {
145       for (String ext : extensions) {
146     if (ext.equalsIgnoreCase(extension)) {
147         return true;
148     }
149       }
150   }
151   return false;
152     }
153 
154     /**
155      * Get the RuleChainVisitor implementation class used when visiting the AST
156      * structure for this Rules for this Language.
157      @return The RuleChainVisitor class.
158      @see RuleChainVisitor
159      */
160     public Class<?> getRuleChainVisitorClass() {
161   return ruleChainVisitorClass;
162     }
163 
164     /**
165      * Gets the list of supported LanguageVersion for this Language.
166      @return The LanguageVersion for this Language.
167      */
168     public List<LanguageVersion> getVersions() {
169   return versions;
170     }
171 
172     /**
173      * Get the current PMD defined default LanguageVersion for this Language.
174      * This is an arbitrary choice made by the PMD product, and can change
175      * between PMD releases.  Every Language has a default version.
176      @return The current default LanguageVersion for this Language.
177      */
178     public LanguageVersion getDefaultVersion() {
179   init();
180   for (LanguageVersion version : getVersions()) {
181       if (version.isDefaultVersion()) {
182     return version;
183       }
184   }
185   throw new IllegalStateException("No default LanguageVersion configured for " this);
186     }
187 
188     /**
189      * Get the LanguageVersion for the version string from this Language.
190      @param version The language version string.
191      @return The corresponding LanguageVersion, <code>null</code> if the
192      * version string is not recognized.
193      */
194     public LanguageVersion getVersion(String version) {
195   init();
196   for (LanguageVersion languageVersion : getVersions()) {
197       if (languageVersion.getVersion().equals(version)) {
198     return languageVersion;
199       }
200   }
201   return null;
202     }
203 
204     /**
205      * A friendly String form of the Language.
206      */
207     @Override
208     public String toString() {
209   return "Language [" + name + "]";
210     }
211 
212     /**
213      * A utility method to find the Languages which have Rule support.
214      @return A List of Languages with Rule support.
215      */
216     public static List<Language> findWithRuleSupport() {
217   List<Language> languages = new ArrayList<Language>();
218   for (Language language : Language.values()) {
219       if (language.getRuleChainVisitorClass() != null) {
220     languages.add(language);
221       }
222   }
223   return languages;
224     }
225 
226     /**
227      * A utility method to find the Languages which are associated with
228      * the given file extension.
229      @param extension The file extension.
230      @return A List of Languages which handle the extension.
231      */
232     public static List<Language> findByExtension(String extension) {
233   List<Language> languages = new ArrayList<Language>();
234   for (Language language : Language.values()) {
235       if (language.hasExtension(extension)) {
236     languages.add(language);
237       }
238   }
239   return languages;
240     }
241 
242     /**
243      * A utility method to find the Language associated with the given
244      * terse name, whatever the case is.
245      @param terseName The Language terse name.
246      @return The Language with this terse name, <code>null</code> if there is
247      * no Language with this terse name.
248      */
249     public static Language findByTerseName(String terseName) {
250   for (Language language : Language.values()) {
251       if (language.getTerseName().equalsIgnoreCase(terseName)) {
252     return language;
253       }
254   }
255   return null;
256     }
257     
258     /**
259      * Return a comma separated list of Language terse names.
260      @param languages The languages.
261      @return Comma separated terse names.
262      */
263     public static String commaSeparatedTerseNames(List<Language> languages) {
264   StringBuilder builder = new StringBuilder();
265   for (Language language : languages) {
266       if (builder.length() 0) {
267     builder.append(", ");
268       }
269       builder.append(language.getTerseName());
270   }
271   return builder.toString();
272     }
273 
274     private static void init() {
275   // Force initialization of the LanguageVersion enum.
276   // This must be done before the versions can be accessed on this enum.
277   LanguageVersion.values();
278     }
279 
280     /**
281      * Return the default language for PMD.
282      @return the proper default language
283      */
284     public static Language getDefaultLanguage() {
285   return Language.JAVA;
286     }
287 }