forked from openhab/static-code-analysis
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial contribution to Compact Profile Check.
Closes openhab#214 Signed-off-by: Svilen Valkanov <svilen.valkanov@musala.com>
- Loading branch information
Svilen Valkanov
committed
Mar 30, 2018
1 parent
b308310
commit ae91c75
Showing
10 changed files
with
479 additions
and
2 deletions.
There are no files selected for viewing
227 changes: 227 additions & 0 deletions
227
...s/checkstyle/src/main/java/org/openhab/tools/analysis/checkstyle/CompactProfileCheck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
/** | ||
* Copyright (c) 2010-2018 by the respective copyright holders. | ||
* | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
*/ | ||
package org.openhab.tools.analysis.checkstyle; | ||
|
||
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.POM_XML_FILE_NAME; | ||
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.TARGET_DIRECTORY; | ||
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.XML_EXTENSION; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
import java.io.StringWriter; | ||
import java.nio.charset.Charset; | ||
import java.nio.file.Path; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
import javax.xml.xpath.XPathConstants; | ||
import javax.xml.xpath.XPathExpression; | ||
import javax.xml.xpath.XPathExpressionException; | ||
|
||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
import org.openhab.tools.analysis.checkstyle.api.AbstractStaticCheck; | ||
import org.w3c.dom.Document; | ||
|
||
import com.google.common.io.Files; | ||
import com.ibm.icu.text.MessageFormat; | ||
import com.puppycrawl.tools.checkstyle.api.CheckstyleException; | ||
import com.puppycrawl.tools.checkstyle.api.FileText; | ||
import com.sun.tools.jdeps.Main; | ||
|
||
/** | ||
* | ||
* Verifies if a project is restricted to specified Java Compact Profile. | ||
* | ||
* Uses the Oracle Java jdpes tool. The check will be skipped, if the tool is | ||
* not present. | ||
* | ||
* This check should be executed after the compilation phase is executed and a | ||
* .jar file is built | ||
* | ||
* @author Svilen Valkanov - Initial contribution | ||
*/ | ||
public class CompactProfileCheck extends AbstractStaticCheck { | ||
|
||
private static final String JDEPS_DOTOUTPUT = "-dotoutput"; | ||
private static final Log logger = LogFactory | ||
.getLog(CompactProfileCheck.class); | ||
private static final String POM_ARTIFACT_ID_XPATH_EXPRESSION = "/project/artifactId/text()"; | ||
private static final String POM_VERSION_XPATH_EXPRESSION = "/project/parent/version/text()"; | ||
private static final String JDEPS_REPORT_DIR_NAME = "jdeps"; | ||
|
||
private static final String JDEPS_RESULT_FILE_EXTENSION = ".dot"; | ||
private static final String JDEPS_PROFILE = "-profile"; | ||
private static final String JDEPS_HELP = "-help"; | ||
private static final Profile DEFAULT_PROFILE = Profile.COMPACT_PROFILE_2; | ||
|
||
// Expected: "checkstyle.compactProfileCheckTest" -> "java.io (compact1)"; | ||
private static final Pattern PROFILE_PATTERN = Pattern | ||
.compile("\\((.*?)\\)"); | ||
private static final Pattern PACKAGE_PATTERN = Pattern | ||
.compile("\"(.*?)\".*\"(.*?)\""); | ||
private static final String MESSAGE_PATTERN = "Package {0} depends on {1}, which is a superset of {2}"; | ||
|
||
boolean isJdepsAvailable = false; | ||
|
||
private Profile maximumProfile = DEFAULT_PROFILE; | ||
|
||
public void setMaximumProfile(String maximumProfile) { | ||
try { | ||
this.maximumProfile = Profile.fromString(maximumProfile); | ||
} catch (IllegalArgumentException e) { | ||
logger.warn("Unknown profile " + maximumProfile | ||
+ ". Fall back to default settings " + DEFAULT_PROFILE); | ||
this.maximumProfile = DEFAULT_PROFILE; | ||
} | ||
} | ||
|
||
public CompactProfileCheck() { | ||
setFileExtensions(XML_EXTENSION); | ||
} | ||
|
||
@Override | ||
public void beginProcessing(String charset) { | ||
StringWriter out = new StringWriter(); | ||
Main.run(new String[]{JDEPS_HELP}, new PrintWriter(out)); | ||
|
||
if (out.toString().contains(JDEPS_PROFILE)) { | ||
isJdepsAvailable = true; | ||
} else { | ||
isJdepsAvailable = false; | ||
logger.warn( | ||
"Jdeps execution will be skipped as the version being used does not support Compact Profile information !"); | ||
} | ||
} | ||
|
||
@Override | ||
protected void processFiltered(File file, FileText fileText) | ||
throws CheckstyleException { | ||
if (file.getName().equals(POM_XML_FILE_NAME) && isJdepsAvailable) { | ||
String jarFileName = getArtifactName(fileText); | ||
|
||
if (jarFileName != null) { | ||
Path targetDir = file.getParentFile().toPath() | ||
.resolve(TARGET_DIRECTORY); | ||
Path jdepsReportDir = targetDir.resolve(JDEPS_REPORT_DIR_NAME); | ||
|
||
String jarFilePath = targetDir.resolve(jarFileName) | ||
.toAbsolutePath().toString(); | ||
String jdepsReportDirPath = jdepsReportDir.toAbsolutePath() | ||
.toString(); | ||
|
||
// Jdeps config | ||
String[] args = new String[4]; | ||
args[0] = JDEPS_PROFILE; | ||
args[1] = JDEPS_DOTOUTPUT; | ||
args[2] = jdepsReportDirPath; | ||
args[3] = jarFilePath; | ||
|
||
StringWriter out = new StringWriter(); | ||
Main.run(args, new PrintWriter(out)); | ||
|
||
if (out.toString().contains("Error")) { | ||
logger.error("Jdeps can not process file '" + jarFilePath | ||
+ "' : " + out.toString()); | ||
return; | ||
} | ||
|
||
analyzeResults(jdepsReportDir | ||
.resolve(jarFileName + JDEPS_RESULT_FILE_EXTENSION)); | ||
} | ||
} | ||
} | ||
|
||
private void analyzeResults(Path jdepsResult) { | ||
try { | ||
for (String line : Files.readLines(jdepsResult.toFile(), | ||
Charset.defaultCharset())) { | ||
|
||
Matcher profileMatcher = PROFILE_PATTERN.matcher(line); | ||
Matcher packageMatcher = PACKAGE_PATTERN.matcher(line); | ||
|
||
if (profileMatcher.find() && packageMatcher.find()) { | ||
String profileString = profileMatcher.group(1); | ||
String sourcePackage = packageMatcher.group(1); | ||
String dependingPackage = packageMatcher.group(2); | ||
|
||
try { | ||
Profile profile = Profile.fromString(profileString); | ||
|
||
// Compare to the configured profile | ||
if (profile.compareTo(maximumProfile) > 0) { | ||
|
||
log(0, MessageFormat.format(MESSAGE_PATTERN, | ||
sourcePackage, dependingPackage, | ||
maximumProfile)); | ||
} | ||
} catch (IllegalArgumentException e) { | ||
logger.error("Unknown Java profile " + profileString, | ||
e); | ||
} | ||
} | ||
} | ||
} catch (IOException e) { | ||
logger.error("Unable to read jdeps output file " | ||
+ jdepsResult.toString(), e); | ||
} | ||
} | ||
|
||
private String getArtifactName(FileText fileText) | ||
throws CheckstyleException { | ||
Document xmlDocument = parseDomDocumentFromFile(fileText); | ||
|
||
try { | ||
XPathExpression artifactIdExpression = compileXPathExpression( | ||
POM_ARTIFACT_ID_XPATH_EXPRESSION); | ||
String artifactID = (String) artifactIdExpression | ||
.evaluate(xmlDocument, XPathConstants.STRING); | ||
|
||
XPathExpression versiondExpression = compileXPathExpression( | ||
POM_VERSION_XPATH_EXPRESSION); | ||
String artifactVersion = (String) versiondExpression | ||
.evaluate(xmlDocument, XPathConstants.STRING); | ||
|
||
return String.format("%s-%s.jar", artifactID, artifactVersion); | ||
} catch (XPathExpressionException e) { | ||
logger.error("Invalid XPath expression ! ", e); | ||
return null; | ||
} | ||
} | ||
|
||
public enum Profile { | ||
|
||
// Enums implement comparable in the order in which the constants are | ||
// declared | ||
COMPACT_PROFILE_1("compact1"), COMPACT_PROFILE_2( | ||
"compact2"), COMPACT_PROFILE_3("compact3"), FULL_SE("Full JRE"); | ||
|
||
private String representation; | ||
|
||
Profile(String string) { | ||
this.representation = string; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return representation; | ||
} | ||
|
||
static Profile fromString(String value) { | ||
for (Profile profile : Profile.values()) { | ||
if (profile.toString().equals(value)) { | ||
return profile; | ||
} | ||
} | ||
throw new IllegalArgumentException( | ||
"No profile with value " + value + " is defined "); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
...yle/src/test/java/org/openhab/tools/analysis/checkstyle/test/CompactProfileCheckTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/** | ||
* Copyright (c) 2010-2018 by the respective copyright holders. | ||
* | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
*/ | ||
package org.openhab.tools.analysis.checkstyle.test; | ||
|
||
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.*; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
|
||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.rules.TemporaryFolder; | ||
import org.openhab.tools.analysis.checkstyle.CompactProfileCheck; | ||
import org.openhab.tools.analysis.checkstyle.CompactProfileCheck.Profile; | ||
import org.openhab.tools.analysis.checkstyle.api.AbstractStaticCheckTest; | ||
|
||
import com.google.common.io.Files; | ||
import com.ibm.icu.text.MessageFormat; | ||
import com.puppycrawl.tools.checkstyle.DefaultConfiguration; | ||
import com.puppycrawl.tools.checkstyle.api.Configuration; | ||
|
||
/** | ||
* Tests for {@link CompactProfileCheck} | ||
* | ||
* @author Svilen Valkanov - Initial contribution | ||
* | ||
*/ | ||
public class CompactProfileCheckTest extends AbstractStaticCheckTest { | ||
|
||
private static final String TEST_BASE_DIR = "compactProfileCheckTest/"; | ||
private static final DefaultConfiguration checkConfig = createCheckConfig( | ||
CompactProfileCheck.class); | ||
private static final String MESSAGE_PATTERN = "Package {0} depends on {1}, which is a superset of {2}"; | ||
|
||
private Log logger = LogFactory.getLog(CompactProfileCheckTest.class); | ||
|
||
@Rule | ||
public TemporaryFolder tempFolder = new TemporaryFolder(new File( | ||
this.getClass().getClassLoader().getResource(".").getPath())); | ||
|
||
@Test | ||
public void testCompactProfileTwoCompatibleJar() throws Exception { | ||
verify("compactProfileTwoCompatible", | ||
"CompactProfileTwoCompatible.java", generateExpectedMessages()); | ||
} | ||
|
||
@Test | ||
public void testCompactProfileThreeCompatibleJar() throws Exception { | ||
String expectedMessage = MessageFormat.format(MESSAGE_PATTERN, | ||
"checkstyle.compactProfileCheckTest", | ||
"javax.xml.crypto (compact3)", Profile.COMPACT_PROFILE_2); | ||
|
||
verify("compactProfileThreeCompatible", | ||
"CompactProfileThreeCompatible.java", | ||
generateExpectedMessages(0, expectedMessage)); | ||
} | ||
|
||
@Test | ||
public void testFullStandardEditionJar() throws Exception { | ||
String expectedMessage = MessageFormat.format(MESSAGE_PATTERN, | ||
"checkstyle.compactProfileCheckTest", "javax.jws (Full JRE)", | ||
Profile.COMPACT_PROFILE_2); | ||
|
||
verify("fullStandardEdition", "FullStandardEdition.java", | ||
generateExpectedMessages(0, expectedMessage)); | ||
} | ||
|
||
private void verify(String testCaseDir, String testSourceFile, | ||
String[] expectedMessages) throws Exception { | ||
String testSourceDirectory = getPath(TEST_BASE_DIR + "/" + testCaseDir); | ||
Path testSourcePath = Paths.get(testSourceDirectory, "src"); | ||
|
||
File targetFolder = tempFolder.newFolder(TARGET_DIRECTORY); | ||
Path targetDirPath = targetFolder.toPath(); | ||
|
||
// compile the classes into tempfolder/target | ||
String compileCommand = String.format("javac -d %s %s%s%s", | ||
targetDirPath.toString(), testSourcePath.toString(), | ||
File.separator, testSourceFile); | ||
|
||
Process compilation = Runtime.getRuntime().exec(compileCommand); | ||
compilation.waitFor(); | ||
debug(compilation, compileCommand); | ||
|
||
// package the compiled classes into jar | ||
String packageCommand = String.format("jar cf %s %s", | ||
targetDirPath.toString() + File.separator | ||
+ "org.openhab.tools.sat-plugin.test-0.9.0.jar", | ||
targetDirPath.toString()); | ||
|
||
Process packaging = Runtime.getRuntime().exec(packageCommand); | ||
packaging.waitFor(); | ||
debug(packaging, packageCommand); | ||
|
||
// Copy the pom.xml. It will be read by the check to find out the jar | ||
// name | ||
File copiedPomFile = tempFolder.newFile(POM_XML_FILE_NAME); | ||
Files.copy(new File(testSourceDirectory, POM_XML_FILE_NAME), | ||
copiedPomFile); | ||
|
||
// execute the check in the temp folder | ||
verify(createChecker(checkConfig), copiedPomFile.getAbsolutePath(), | ||
expectedMessages); | ||
|
||
} | ||
|
||
private void debug(Process process, String commandLine) throws IOException { | ||
String s = null; | ||
BufferedReader stdInput = new BufferedReader( | ||
new InputStreamReader(process.getInputStream())); | ||
BufferedReader stdError = new BufferedReader( | ||
new InputStreamReader(process.getErrorStream())); | ||
|
||
logger.debug("stdInput: " + commandLine); | ||
while ((s = stdInput.readLine()) != null) { | ||
logger.debug(s); | ||
} | ||
logger.debug("stdError: " + commandLine); | ||
while ((s = stdError.readLine()) != null) { | ||
logger.error(s); | ||
} | ||
} | ||
|
||
@Override | ||
protected DefaultConfiguration createCheckerConfig(Configuration config) { | ||
DefaultConfiguration defaultConfiguration = new DefaultConfiguration( | ||
"root"); | ||
defaultConfiguration.addChild(config); | ||
return defaultConfiguration; | ||
} | ||
} |
Oops, something went wrong.