Java tutorial
/* * Sonar CodeNarc Converter * Copyright (C) 2011-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.plugins.gosu.codenarc; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.commons.lang.StringUtils; import org.codenarc.rule.AbstractRule; import org.sonar.plugins.gosu.codenarc.apt.AptResult; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class Rule { public final AbstractRule ruleInstance; public final String key; public final String internalKey; public final String name; public final String description; public final String severity; public final String version; public final Set<String> tags; public final Set<RuleParameter> parameters; // SONARGROOV-40 private static final String[] FIXED_RULES_WITH_NULL_PARAMETERS = { "org.codenarc.rule.design.PrivateFieldCouldBeFinalRule", "org.codenarc.rule.generic.IllegalRegexRule", "org.codenarc.rule.generic.RequiredRegexRule", "org.codenarc.rule.generic.RequiredStringRule", "org.codenarc.rule.generic.StatelessClassRule", "org.codenarc.rule.generic.IllegalPackageReferenceRule", "org.codenarc.rule.generic.IllegalClassReferenceRule", "org.codenarc.rule.generic.IllegalClassMemberRule", "org.codenarc.rule.generic.IllegalStringRule", "org.codenarc.rule.generic.IllegalSubclassRule", "org.codenarc.rule.grails.GrailsPublicControllerMethodRule", "org.codenarc.rule.junit.SpockIgnoreRestUsedRule", "org.codenarc.rule.junit.JUnitPublicPropertyRule", "org.codenarc.rule.naming.AbstractClassNameRule", "org.codenarc.rule.naming.FieldNameRule", "org.codenarc.rule.naming.InterfaceNameRule", "org.codenarc.rule.naming.MethodNameRule", "org.codenarc.rule.naming.ParameterNameRule", "org.codenarc.rule.naming.PropertyNameRule", "org.codenarc.rule.naming.VariableNameRule", "org.codenarc.rule.naming.PackageNameMatchesFilePathRule", "org.codenarc.rule.size.CyclomaticComplexityRule", "org.codenarc.rule.size.MethodSizeRule", "org.codenarc.rule.size.CrapMetricRule", "org.codenarc.rule.size.AbcMetricRule", "org.codenarc.rule.unused.UnusedVariableRule" }; public Rule(Class<? extends AbstractRule> ruleClass, String since, Properties props, Map<String, AptResult> parametersByRule) throws Exception { ruleInstance = ruleClass.newInstance(); key = ruleClass.getCanonicalName(); internalKey = StringUtils.removeEnd(ruleClass.getSimpleName(), "Rule"); name = cleanName(internalKey); severity = severity(ruleInstance.getPriority()); tags = getTags(key, internalKey); version = since; AptResult dataFromAptFile = parametersByRule.get(internalKey); String descriptionFromProperty = props.getProperty(internalKey + ".description.html"); description = extractDescription(dataFromAptFile, descriptionFromProperty); parameters = extractParameters(dataFromAptFile, descriptionFromProperty); } private static String cleanName(String internalKey) { String result = StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(internalKey), ' '); return result.replace("J Unit", "JUnit"); } private Set<RuleParameter> extractParameters(AptResult results, String description) { Map<String, RuleParameter> extractedParameters = Maps.newHashMap(); addParameters(results, extractedParameters); String[] params1 = StringUtils.substringsBetween(description, "<em>", "</em> property"); if (params1 != null) { for (int i = 0; i < params1.length; i++) { String paramName = params1[i]; if (paramName.contains("<em>")) { params1[i] = paramName.substring(paramName.lastIndexOf("<em>") + 4); } } addParameters(params1, ruleInstance, extractedParameters); } String[] params2 = StringUtils.substringsBetween(description, "configured in <em>", "</em>"); if (params2 != null) { addParameters(params2, ruleInstance, extractedParameters); } if (StringUtils.contains(description, "length property")) { addParameter("length", ruleInstance, extractedParameters); } if (StringUtils.contains(description, "sameLine property")) { addParameter("sameLine", ruleInstance, extractedParameters); } return Sets.newHashSet(extractedParameters.values()); } private static void addParameters(AptResult results, Map<String, RuleParameter> parameters) { if (results.hasParameters()) { for (RuleParameter param : results.getParameters()) { parameters.put(param.key, param); } } } private void addParameters(String[] parameterNames, AbstractRule ruleInstance, Map<String, RuleParameter> parameters) { for (String parameterName : parameterNames) { addParameter(parameterName, ruleInstance, parameters); } } private void addParameter(String parameterName, AbstractRule ruleInstance, Map<String, RuleParameter> parameters) { RuleParameter current = parameters.get(parameterName); RuleParameter parameter = new RuleParameter(parameterName); parameter.defaultValue = extractDefaultValue(parameterName, ruleInstance); if (current == null) { current = parameter; } else { current.merge(parameter); } parameters.put(current.key, current); } private String extractDefaultValue(String parameterName, AbstractRule ruleInstance) { String result = ""; try { // Hack to get the default value Field f = ruleInstance.getClass().getDeclaredField(parameterName); f.setAccessible(true); Object value = f.get(ruleInstance); if (value != null) { return value.toString(); } } catch (NoSuchFieldException e) { // do nothing, there is probably no default value } catch (IllegalArgumentException e) { // do nothing, that's probably not the correct name } catch (IllegalAccessException e) { // do nothing, can not access field } return result; } private String extractDescription(AptResult dataFromAptFile, String property) { String result = property; if (dataFromAptFile != null && StringUtils.isNotBlank(dataFromAptFile.getDescription())) { result = dataFromAptFile.getDescription(); } return cleanDescription(result); } private static String severity(int priority) { switch (priority) { case 1: return "INFO"; case 2: return "MINOR"; case 3: return "MAJOR"; default: throw new IllegalStateException("Should never happen"); } } private static Set<String> getTags(String key, String internalKey) { String[] split = key.split("\\."); String codeNarcCategory = split[split.length - 2]; Set<String> results = Sets.newHashSet(); switch (codeNarcCategory) { case "unnecessary": results.add("clumsy"); break; case "formatting": case "naming ": results.add("convention"); break; case "concurrency": results.add("multi-threading"); break; case "exceptions": results.add("error-handling"); break; case "basic": results.addAll(handleBasicCategory(internalKey)); break; case "grails": case "groovyism": case "junit": case "design": results.add(codeNarcCategory); break; default: results.add("bug"); break; } return results; } private static Set<String> handleBasicCategory(String internalKey) { Set<String> results = Sets.newHashSet(); if (internalKey.startsWith("Empty")) { results.add("unused"); } else if (internalKey.startsWith("Broken")) { results.add("bug"); } else if (internalKey.startsWith("Equals")) { results.add("pitfall"); } else if (internalKey.contains("Get") && !internalKey.startsWith("Get")) { results.add("bug"); } else if (internalKey.endsWith("FinallyBlock")) { results.add("error-handling"); } else { results.addAll(handleParticularCases(internalKey)); } return results; } private static Set<String> handleParticularCases(String internalKey) { Set<String> results = Sets.newHashSet(); if ("DeadCode".equals(internalKey)) { results.add("unused"); } else if ("ExplicitGarbageCollection".equals(internalKey)) { results.add("unpredictable"); } else if ("HardCodedWindowsFileSeparator".equals(internalKey) || "HardCodedWindowsRootDirectory".equals(internalKey)) { results.add("pitfall"); } else if ("ForLoopShouldBeWhileLoop".equals(internalKey)) { results.add("clumsy"); } else if ("ClassForName".equals(internalKey)) { results.add("leak"); results.add("owasp-a1"); } else { results.add("bug"); } return results; } private String cleanDescription(String description) { String copy = description; String[] refToParams = StringUtils.substringsBetween(description, " (${rule.", "})"); if (refToParams != null) { for (String ref : refToParams) { String paramRef = " (${rule." + ref + "})"; copy = copy.replace(paramRef, ""); } } return handleUrls(copy); } /** * Covers URLs such as: * {{http://blog.bjhargrave.com/2007/09/classforname-caches-defined-class-in.html}} <--- direct link * {{{http://en.wikipedia.org/wiki/Double-checked_locking}}} <--- direct link * {{{http://jira.codehaus.org/browse/JETTY-352}JETTY-352}} <--- renamed link */ private String handleUrls(String description) { String result = description; String[] urls = extractUrls(description); if (urls.length > 0) { for (String url : urls) { String copy = url; boolean trailingAcc = false; if (!copy.startsWith("{")) { copy = "<a>" + copy + "</a>"; } else if (copy.startsWith("{http")) { if ('}' == result.charAt(result.indexOf(copy) + copy.length() + 2)) { trailingAcc = true; copy = "<a>" + copy.substring(1) + "</a>"; } else { copy = "<a href=\"" + copy.replace("{", "").replace("}", "\">") + "</a>"; } } else if (copy.startsWith("{./")) { copy = "<a href=\"http://codenarc.sourceforge.net/" + copy.replace("{./", "").replace("}", "\">") + "</a>"; } result = result.replace("{{" + url + "}}" + (trailingAcc ? "}" : ""), copy); } } return result; } private static String[] extractUrls(String description) { List<String> urls = Lists.newArrayList(); int index = 0; while (index < description.length()) { int start = description.indexOf("{{", index); if (start == -1) { break; } int end = description.indexOf("}}", start); if (end == -1) { break; } urls.add(description.substring(start + 2, end)); index = end; } if (urls.isEmpty()) { return new String[] {}; } return urls.toArray(new String[urls.size()]); } public String fixedRuleKey() { if (hasNullParameters() && isPartOfFixedRules()) { return key + ".fixed"; } return key; } private boolean isPartOfFixedRules() { return Arrays.stream(FIXED_RULES_WITH_NULL_PARAMETERS).anyMatch(key::equals); } private boolean hasNullParameters() { for (RuleParameter parameter : parameters) { if ("null".equals(parameter.defaultValue)) { return true; } } return false; } }