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