Skip to content

Commit f0f0af3

Browse files
committed
Moved all non-code artifacts required at runtime to src/main/resources to make JAR self-contained; reworked file loading accordingly
1 parent d7d4424 commit f0f0af3

28 files changed

+243
-179
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ production/
1616
.factorypath
1717
.class
1818
bin/
19-
src/main/java/cli/docs/config.properties
19+
src/main/resources/config.properties

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Currently, RESTRuler can parse the following Web API description languages:
66
* [OpenAPI v2](https://swagger.io/specification/v2/)
77
* [OpenAPI v3](https://github.com/OAI/OpenAPI-Specification)
88

9-
RESTRuler has been developed in the [Empirical Software Engineering Group](https://www.iste.uni-stuttgart.de/ese) at the University of Stuttgart, Germany, as a research prototype written in Java.
9+
RESTRuler has been developed in the [Empirical Software Engineering Group](https://www.iste.uni-stuttgart.de/ese) at the University of Stuttgart, Germany, as a research prototype written in Java (version >=18 needed).
1010
It is a command-line tool that takes the path or URL to an OpenAPI definition file as input and displays a list of design rule violations as output.
1111
Optionally, a Markdown report file can be generated with additional details and improvement suggestions.
1212

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
id "com.adarshr.test-logger" version "4.0.0"
66
}
77

8-
version = "2.0.0"
8+
version = "2.1.0"
99
group = "rest-ruler"
1010

1111
repositories {

docs/architecture.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ A good overview of the implemented rules can be found [here](./rules/README.md).
8989

9090
## Apache OpenNLP
9191
- [Apache OpenNLP](https://opennlp.apache.org)
92-
- [Model](models/en-pos-maxent.bin)
92+
- [Model](src/main/resources/models/en-pos-maxent.bin)
9393

9494
## Weka
9595

9696
- [Weka](https://www.cs.waikato.ac.nz/ml/weka/)
97-
- [Model](models/request_model.dat)
97+
- [Model](src/main/resources/models/request_model.dat)

docs/rules/implemented-rules/CRUD-function-names-should-not-be-used-in-URIs.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The following anti-patterns exemplify what not to do:
4949
* The parameters in curly brackets are excluded from the path and are therefore currently not checked
5050
* If more than these previously defined words are to be considered CRUD violations, perform the following steps:
5151
1. Add the appropriate words to the attribute `CRUD_OPERATIONS` in the `./src/main/java/cli/rule/rules/CRUDRule.java`
52-
2. Mine the words that have the appropriate words as substring from `./src/main/java/cli/docs/wordninja_words.txt` and add them to the list `./src/main/java/cli/docs/CRUD_words.txt` (Exclude similar words to still detect the violation; e.g. "gets" from "get")
52+
2. Mine the words that have the appropriate words as substring from `./src/main/resources/wordninja_words.txt` and add them to the list `./src/main/resources/CRUD_words.txt` (Exclude similar words to still detect the violation; e.g. "gets" from "get")
5353

5454
### Future work
5555

docs/rules/implemented-rules/File-extensions-should-not-be-included-in-URIs.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ On the Web, the period (.) character is commonly used to separate the file name
2727

2828
### What is checked
2929

30-
The Path is divided into the individual segments. Then, to check if a segment ends with a dot and a file extension (.file-extension), a list [2] of 838 different file extensions is searched. If one of them matches the end of the segment, a rule violation is returned. This list can be extended very easily if file extensions are missed. The list is located here: `./src/main/java/cli/docs/file_extensions.txt`.
30+
The Path is divided into the individual segments. Then, to check if a segment ends with a dot and a file extension (.file-extension), a list [2] of 838 different file extensions is searched. If one of them matches the end of the segment, a rule violation is returned. This list can be extended very easily if file extensions are missed. The list is located here: `./src/main/resources/file_extensions.txt`.
3131

3232
### What is not checked
3333

3434
* The path needs to end with .file-extension. If the path does not end with this, the rule is not violated.
35-
* It is possible that individual file extensions are not recognized because they have not been recorded in the system. To create a rule violation anyway, simply add the non-existing extensions to the list: `./src/main/java/cli/docs/file_extensions.txt`
35+
* It is possible that individual file extensions are not recognized because they have not been recorded in the system. To create a rule violation anyway, simply add the non-existing extensions to the list: `./src/main/resources/file_extensions.txt`
3636

3737
### Future work
3838

docs/rules/implemented-rules/Request-Methods.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Description from Massé [1].
3232
### What is checked:
3333

3434
* For each request, we check if the description or summary attribute corresponds to the request type.
35-
* To implement this, we used Weka [2] to train a Naive Bayes Multinomial classifier (`models/request_model.dat`) that predicts the request type based on the description or the summary. We curated a dataset of over 3k instances for this (`models/request-model-training-data.txt`).
35+
* To implement this, we used Weka [2] to train a Naive Bayes Multinomial classifier (`src/main/resources/models/request_model.dat`) that predicts the request type based on the description or the summary. We curated a dataset of over 3k instances for this (`model-training/request-model-training-data.txt`).
3636
* If the prediction doesn't match with the given request type, we throw a violation.
3737

3838
### What is not checked:
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/main/java/cli/rule/Utility.java

+66-43
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,66 @@
11
package cli.rule;
22

3-
import cli.rule.rules.SingularDocumentNameRule;
3+
import java.io.BufferedReader;
4+
import java.io.File;
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.io.InputStreamReader;
8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.Comparator;
11+
import java.util.HashMap;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.Scanner;
15+
import java.util.logging.Level;
16+
import java.util.logging.Logger;
17+
import java.util.stream.Collectors;
18+
import org.apache.commons.lang3.tuple.ImmutablePair;
419
import com.google.common.collect.Lists;
20+
import cli.rule.rules.SingularDocumentNameRule;
521
import io.swagger.parser.OpenAPIParser;
622
import io.swagger.v3.oas.models.OpenAPI;
723
import io.swagger.v3.parser.core.models.SwaggerParseResult;
824
import opennlp.tools.postag.POSModel;
925
import opennlp.tools.postag.POSTaggerME;
1026
import opennlp.tools.tokenize.SimpleTokenizer;
11-
import org.apache.commons.lang3.tuple.ImmutablePair;
12-
13-
import java.io.*;
14-
import java.nio.charset.Charset;
15-
import java.nio.file.Files;
16-
import java.nio.file.Paths;
17-
import java.util.*;
18-
import java.util.logging.Level;
19-
import java.util.logging.Logger;
20-
import java.util.stream.Collectors;
21-
import java.util.stream.Stream;
2227

2328
public class Utility {
2429
private static final Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
25-
private static final String PATH_TO_ENGLISH_DICTIONARY =
26-
"src/main/java/cli/docs/wordninja_words.txt";
27-
public static final String MODELS_EN_POS_MAXENT_BIN = "models/en-pos-maxent.bin";
30+
private static final String PATH_TO_ENGLISH_DICTIONARY = "/wordninja_words.txt";
31+
32+
public static final String MODELS_EN_POS_MAXENT_BIN = "/models/en-pos-maxent.bin";
2833

2934
private Utility() {
3035
throw new IllegalStateException("Utility class");
3136
}
3237

3338
public static boolean getPathSegmentContained(String word, String filePath) {
3439
boolean isWordInDictionary = false;
35-
try (FileReader fileReader = new FileReader(filePath)) {
36-
try (Scanner scanner = new Scanner(fileReader)) {
37-
while (scanner.hasNext()) {
38-
String wordFromDictionary = scanner.next();
39-
if (word.toLowerCase().contains(wordFromDictionary)) {
40-
isWordInDictionary = true;
41-
break;
42-
}
40+
try (InputStream is = Utility.class.getResourceAsStream(filePath);
41+
BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
42+
String wordFromDictionary;
43+
while ((wordFromDictionary = br.readLine()) != null) {
44+
if (word.toLowerCase().contains(wordFromDictionary)) {
45+
isWordInDictionary = true;
46+
break;
4347
}
4448
}
4549
} catch (Exception e) {
46-
logger.severe("Error on checking if a word is contained in a dictionary: " + e.getMessage());
50+
logger.severe(
51+
"Error on checking if a word is contained in a dictionary: " + e.getMessage());
4752
}
4853
return isWordInDictionary;
4954
}
5055

5156
public static boolean getPathSegmentMatch(String word, String filePath) {
5257
boolean isWordInDictionary = false;
5358
try (Scanner scanner = new Scanner(new File(filePath))) {
54-
if (scanner.useDelimiter("\\Z").next().matches(word)) isWordInDictionary = true;
59+
if (scanner.useDelimiter("\\Z").next().matches(word))
60+
isWordInDictionary = true;
5561
} catch (Exception e) {
56-
logger.severe("Error on checking if a word is contained in a dictionary: " + e.getMessage());
62+
logger.severe(
63+
"Error on checking if a word is contained in a dictionary: " + e.getMessage());
5764
}
5865
return isWordInDictionary;
5966
}
@@ -65,6 +72,7 @@ public static OpenAPI getOpenAPI(String path) {
6572

6673
/**
6774
* Method to change the switchPathSegment from plural to singular a vice versa.
75+
*
6876
* @param equals
6977
* @return
7078
*/
@@ -78,17 +86,17 @@ public static String getControlPathSegmentForRule(boolean equals) {
7886

7987
/**
8088
* Get a token from a word using the nlp apache pos tagger library.
89+
*
8190
* @param pathSegment
8291
* @return
8392
*/
84-
public static String getTokenNLP(String pathSegment){
85-
if(pathSegment.equals("")){
93+
public static String getTokenNLP(String pathSegment) {
94+
if (pathSegment.equals("")) {
8695
return null;
8796
}
8897
SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE;
8998
String[] tokens = tokenizer.tokenize(pathSegment);
90-
try(InputStream modelIn = new FileInputStream(
91-
MODELS_EN_POS_MAXENT_BIN);) {
99+
try (InputStream modelIn = Utility.class.getResourceAsStream(MODELS_EN_POS_MAXENT_BIN);) {
92100
POSModel posModel = new POSModel(modelIn);
93101
POSTaggerME posTagger = new POSTaggerME(posModel);
94102
String tags[] = posTagger.tag(tokens);
@@ -102,6 +110,7 @@ public static String getTokenNLP(String pathSegment){
102110

103111
/**
104112
* Given a word check if it is plural or singular based on token.
113+
*
105114
* @param token
106115
* @return
107116
*/
@@ -119,6 +128,7 @@ public static String getTokenFromWord(String token) {
119128

120129
/**
121130
* Split a string into words if possible using an english dictionary to match the words.
131+
*
122132
* @param sentence
123133
* @return
124134
* @throws IOException
@@ -127,22 +137,27 @@ public static List<String> splitContiguousWords(String sentence) throws IOExcept
127137
String splitRegex = "[^a-zA-Z0-9']+";
128138
Map<String, Number> wordCost = new HashMap<>();
129139
List<String> dictionaryWords = new ArrayList<>();
130-
Stream<String> stringLines = null;
140+
InputStream is = null;
141+
131142
try {
132-
stringLines = Files.lines(Paths.get(PATH_TO_ENGLISH_DICTIONARY), Charset.defaultCharset());
133-
dictionaryWords = stringLines.collect(Collectors.toList());
143+
is = Utility.class.getResourceAsStream(PATH_TO_ENGLISH_DICTIONARY);
144+
BufferedReader br = new BufferedReader(new InputStreamReader(is));
145+
dictionaryWords = br.lines().collect(Collectors.toList());
134146
} catch (Exception e) {
135147
logger.log(Level.SEVERE, "Error on getting the english dictionary file {e}", e);
136148
} finally {
137-
if (stringLines != null) stringLines.close();
149+
if (is != null) {
150+
is.close();
151+
}
138152
}
139153

140154
double naturalLogDictionaryWordsCount = Math.log(dictionaryWords.size());
141155
long wordIdx = 0;
142156
for (String word : dictionaryWords) {
143157
wordCost.put(word, Math.log(++wordIdx * naturalLogDictionaryWordsCount));
144158
}
145-
int maxWordLength = Collections.max(dictionaryWords, Comparator.comparing(String::length)).length();
159+
int maxWordLength =
160+
Collections.max(dictionaryWords, Comparator.comparing(String::length)).length();
146161
List<String> splitWords = new ArrayList<>();
147162
for (String partSentence : sentence.split(splitRegex)) {
148163
splitWords.add(split(partSentence, wordCost, maxWordLength));
@@ -152,12 +167,14 @@ public static List<String> splitContiguousWords(String sentence) throws IOExcept
152167

153168
/**
154169
* Split a string into sub strings.
170+
*
155171
* @param partSentence
156172
* @param wordCost
157173
* @param maxWordLength
158174
* @return
159175
*/
160-
public static String split(String partSentence, Map<String, Number> wordCost, int maxWordLength) {
176+
public static String split(String partSentence, Map<String, Number> wordCost,
177+
int maxWordLength) {
161178
List<ImmutablePair<Number, Number>> cost = new ArrayList<>();
162179
cost.add(new ImmutablePair<>(0, 0));
163180
for (int index = 1; index < partSentence.length() + 1; index++) {
@@ -166,7 +183,8 @@ public static String split(String partSentence, Map<String, Number> wordCost, in
166183
int idx = partSentence.length();
167184
List<String> output = new ArrayList<>();
168185
while (idx > 0) {
169-
ImmutablePair<Number, Number> candidate = bestMatch(partSentence, cost, idx, wordCost, maxWordLength);
186+
ImmutablePair<Number, Number> candidate =
187+
bestMatch(partSentence, cost, idx, wordCost, maxWordLength);
170188
Number candidateCost = candidate.getKey();
171189
Number candidateIndexValue = candidate.getValue();
172190
if (candidateCost.doubleValue() != cost.get(idx).getKey().doubleValue())
@@ -175,7 +193,9 @@ public static String split(String partSentence, Map<String, Number> wordCost, in
175193
String token = partSentence.substring(idx - candidateIndexValue.intValue(), idx);
176194
if (token.equals("'") && !output.isEmpty()) {
177195
String lastWord = output.get(output.size() - 1);
178-
if (lastWord.equalsIgnoreCase("'s") || (Character.isDigit(partSentence.charAt(idx - 1)) && Character.isDigit(lastWord.charAt(0)))) {
196+
if (lastWord.equalsIgnoreCase("'s")
197+
|| (Character.isDigit(partSentence.charAt(idx - 1))
198+
&& Character.isDigit(lastWord.charAt(0)))) {
179199
output.set(output.size() - 1, token + lastWord);
180200
newToken = false;
181201
}
@@ -190,19 +210,22 @@ public static String split(String partSentence, Map<String, Number> wordCost, in
190210

191211
/**
192212
* Get the best match for a word.
213+
*
193214
* @param partSentence
194215
* @param cost
195216
* @param index
196217
* @param wordCost
197218
* @param maxWordLength
198219
* @return
199220
*/
200-
public static ImmutablePair<Number, Number> bestMatch(String partSentence, List<ImmutablePair<Number, Number>> cost,
201-
int index, Map<String, Number> wordCost, int maxWordLength) {
202-
List<ImmutablePair<Number, Number>> candidates = Lists.reverse(cost.subList(Math.max(0,
203-
index - maxWordLength), index));
221+
public static ImmutablePair<Number, Number> bestMatch(String partSentence,
222+
List<ImmutablePair<Number, Number>> cost, int index, Map<String, Number> wordCost,
223+
int maxWordLength) {
224+
List<ImmutablePair<Number, Number>> candidates =
225+
Lists.reverse(cost.subList(Math.max(0, index - maxWordLength), index));
204226
int enumerateIdx = 0;
205-
ImmutablePair<Number, Number> minPair = new ImmutablePair<>(Integer.MAX_VALUE, enumerateIdx);
227+
ImmutablePair<Number, Number> minPair =
228+
new ImmutablePair<>(Integer.MAX_VALUE, enumerateIdx);
206229
for (ImmutablePair<Number, Number> pair : candidates) {
207230
++enumerateIdx;
208231
String subsequence = partSentence.substring(index - enumerateIdx, index).toLowerCase();

src/main/java/cli/rule/rules/CRUDRule.java

+24-17
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
package cli.rule.rules;
22

3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Set;
38
import cli.analyzer.RestAnalyzer;
49
import cli.rule.IRestRule;
510
import cli.rule.Utility;
611
import cli.rule.Violation;
7-
import cli.rule.constants.*;
12+
import cli.rule.constants.ErrorMessage;
13+
import cli.rule.constants.RuleCategory;
14+
import cli.rule.constants.RuleSeverity;
15+
import cli.rule.constants.RuleSoftwareQualityAttribute;
816
import cli.utility.Output;
9-
1017
import io.swagger.v3.oas.models.OpenAPI;
1118

12-
import java.util.*;
13-
1419
/**
1520
* Implementation of the rule: Underscores (_) should not be used in URI.
1621
*/
1722
public class CRUDRule implements IRestRule {
1823
private static final String TITLE = "CRUD function names should not be used in URIs";
1924
private static final RuleCategory CATEGORY = RuleCategory.URIS;
2025
private static final RuleSeverity SEVERITY = RuleSeverity.ERROR;
21-
private static final List<RuleSoftwareQualityAttribute> SOFTWARE_QUALITY_ATTRIBUTE = Arrays
22-
.asList(RuleSoftwareQualityAttribute.USABILITY, RuleSoftwareQualityAttribute.MAINTAINABILITY);
23-
private static final String[] CRUD_OPERATIONS = { "get", "post", "delete", "put", "create", "read", "update",
24-
"patch", "insert", "select", "fetch", "purge", "retrieve", "add" };
25-
private static final String PATH_TO_CRUD_DICTIONARY = "src/main/java/cli/docs/CRUD_words.txt";
26+
private static final List<RuleSoftwareQualityAttribute> SOFTWARE_QUALITY_ATTRIBUTE =
27+
Arrays.asList(RuleSoftwareQualityAttribute.USABILITY,
28+
RuleSoftwareQualityAttribute.MAINTAINABILITY);
29+
private static final String[] CRUD_OPERATIONS = {"get", "post", "delete", "put", "create",
30+
"read", "update", "patch", "insert", "select", "fetch", "purge", "retrieve", "add"};
31+
private static final String PATH_TO_CRUD_DICTIONARY = "/CRUD_words.txt";
2632
private final List<Violation> violationList = new ArrayList<>();
2733
private boolean isActive;
2834

@@ -61,8 +67,7 @@ public void setIsActive(boolean isActive) {
6167
}
6268

6369
/**
64-
* Checks if there is a violation against the CRUD rule. All paths and base URLs
65-
* are checked.
70+
* Checks if there is a violation against the CRUD rule. All paths and base URLs are checked.
6671
*
6772
* @param openAPI the definition that will be checked against the rule.
6873
* @return the list of violations.
@@ -101,16 +106,18 @@ public List<Violation> checkViolation(OpenAPI openAPI) {
101106
* Checks if the segment contains a CRUD operation
102107
*
103108
* @param segment the currently examined segment
104-
* @param path the whole request path
109+
* @param path the whole request path
105110
*/
106111
private void checkCRUDInSegment(String segment, String path) {
107112
for (String crudOperation : CRUD_OPERATIONS) {
108113
if (segment.toLowerCase().contains(crudOperation)) {
109-
this.violationList.add(new Violation(this, RestAnalyzer.locMapper.getLOCOfPath(path), "URIS should not be " +
110-
"used " + "to " + "indicate that a CRUD function (" + crudOperation.toUpperCase() + ") is "
111-
+ "performed, " + "instead HTTP request methods should be used for this.", path,
112-
ErrorMessage.CRUD));
114+
this.violationList.add(new Violation(this,
115+
RestAnalyzer.locMapper.getLOCOfPath(path),
116+
"URIS should not be " + "used " + "to " + "indicate that a CRUD function ("
117+
+ crudOperation.toUpperCase() + ") is " + "performed, "
118+
+ "instead HTTP request methods should be used for this.",
119+
path, ErrorMessage.CRUD));
113120
}
114121
}
115122
}
116-
}
123+
}

0 commit comments

Comments
 (0)