diff --git a/CryslParser/pom.xml b/CryslParser/pom.xml new file mode 100644 index 0000000..814779a --- /dev/null +++ b/CryslParser/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + de.darmstadt.tu.crossing.CrySL + de.darmstadt.tu.crossing.CrySL.parent + 3.0.2 + ../pom.xml + + + CrySLParser + + CrySL-Parser + CrySL domain-specific language + https://github.com/CROSSINGTUD/CryptSL + + + + Eclipse Public License - v2.0 + https://www.eclipse.org/legal/epl-2.0/ + + + + + + CogniCrypt + CogniCrypt + cognicrypt@eim.upb.de + + + + + scm:git:git@github.com:CROSSINGTUD/CryptSL.git + scm:git:ssh://github.com:CROSSINGTUD/CryptSL.git + https://github.com/CROSSINGTUD/CryptSL + + + + + ${project.groupId} + de.darmstadt.tu.crossing.CrySL + ${project.version} + + + org.eclipse.xtext + org.eclipse.xtext + ${xtextVersion} + + + org.eclipse.xtext + org.eclipse.xtext.xbase + ${xtextVersion} + + + org.eclipse.xtext + org.eclipse.xtext.common.types + ${xtextVersion} + + + org.eclipse.emf + org.eclipse.emf.common + ${emfVersion} + + + org.eclipse.emf + org.eclipse.emf.ecore + ${emfVersion} + + + javax.servlet + javax.servlet-api + 4.0.1 + + + org.slf4j + slf4j-api + 2.0.16 + + + org.slf4j + slf4j-simple + 2.0.16 + + + junit + junit + 4.13.2 + test + + + diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/CrySLParser.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/CrySLParser.java new file mode 100644 index 0000000..e7cf8f3 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/CrySLParser.java @@ -0,0 +1,192 @@ +package de.darmstadt.tu.crossing; + +import de.darmstadt.tu.crossing.parsing.CrySLException; +import de.darmstadt.tu.crossing.parsing.CrySLModelReader; +import de.darmstadt.tu.crossing.rule.CrySLRule; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CrySLParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(CrySLParser.class); + private static final String CRYSL_FILE_ENDING = ".crysl"; + + /** + * Reads the rules from a specific path. The path can either direct to a directory containing + * the .crysl files or a .zip file. + * + * @param path path to a directory or .zip file containing the .crysl files + * @return the {@link CrySLRule} objects from the path + * @throws IOException if there are problems reading the files + */ + public Collection parseRulesFromPath(String path) throws IOException { + if (isZipFile(path)) { + return parseRulesFromZipArchive(path); + } + + return parseRulesFromDirectory(path); + } + + public Collection parseRulesFromDirectory(String path) throws IOException { + File directory = new File(path); + if (!directory.exists()) { + throw new FileNotFoundException("Directory " + path + " does not exist"); + } + + if (!directory.isDirectory()) { + throw new IOException(path + " is not a directory"); + } + + Collection files = Arrays.asList(directory.listFiles()); + return parseRulesFromFiles(files); + } + + public Collection parseRulesFromFiles(Collection files) { + Collection result = new HashSet<>(); + + for (File file : files) { + try { + CrySLRule rule = parseRuleFromFile(file); + + if (result.contains(rule)) { + LOGGER.warn("Rule for class {} appears multiple times", rule.getClassName()); + continue; + } + + result.add(rule); + } catch (CrySLException e) { + LOGGER.error(e.getMessage()); + } + } + return result; + } + + public CrySLRule parseRuleFromFile(File file) throws CrySLException { + String fileName = file.getName(); + if (!fileName.endsWith(CRYSL_FILE_ENDING)) { + throw new CrySLException( + "The extension of " + fileName + " does not match " + CRYSL_FILE_ENDING); + } + + CrySLModelReader modelReader = new CrySLModelReader(); + return modelReader.readRule(file); + } + + private boolean isZipFile(String path) { + File file = new File(path); + + // Copied from + // https://stackoverflow.com/questions/33934178/how-to-identify-a-zip-file-in-java + int fileSignature; + + try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { + fileSignature = raf.readInt(); + } catch (IOException e) { + return false; + } + return fileSignature == 0x504B0304 + || fileSignature == 0x504B0506 + || fileSignature == 0x504B0708; + } + + public Collection parseRulesFromZipArchive(String path) throws IOException { + Collection result = new HashSet<>(); + File file = new File(path); + + try (ZipFile zipFile = new ZipFile(file)) { + Enumeration entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + + if (entry.isDirectory()) { + continue; + } + + try { + CrySLRule rule = parseRuleFromZipEntry(entry, zipFile, file); + result.add(rule); + } catch (CrySLException e) { + LOGGER.error(e.getMessage()); + } + } + } + return result; + } + + private CrySLRule parseRuleFromZipEntry(ZipEntry entry, ZipFile zipFile, File file) + throws CrySLException { + String entryName = entry.getName(); + if (entry.isDirectory() || !entryName.endsWith(CRYSL_FILE_ENDING)) { + throw new CrySLException("ZIP entry " + entryName + " is not a CrySL file"); + } + + try { + String name = createUniqueZipEntryName(file, entry); + CrySLModelReader reader = new CrySLModelReader(); + + InputStream inputStream = zipFile.getInputStream(entry); + CrySLRule rule = reader.readRule(inputStream, name); + inputStream.close(); + + return rule; + } catch (IOException ex) { + throw new CrySLException( + "Could not read file " + + entry.getName() + + " from Zip archive " + + ex.getMessage()); + } + } + + /** + * For zip file entries there is no real URI. Using the raw absolute path of the zip file will + * cause an exception when trying to resolve/create the resource in the {@link + * CrySLModelReader#readRule(File)} methods. Solution: Create a custom URI with the following + * scheme: uri := [HexHashedAbsoluteZipFilePath][SystemFileSeparator][ZipEntryName] This scheme + * has the properties that it still is unique system-wide, The hash will be the same for the + * same file, so you could know if two rules come from the same ruleset file, and you still can + * get the information of the zipped file. + * + * @param zipFile the File that holds the zip archive + * @param zipEntry the Zip entry to create the name for + * @return the unique name + */ + private static String createUniqueZipEntryName(File zipFile, ZipEntry zipEntry) { + StringBuilder sb = new StringBuilder(); + + MessageDigest messageDigest; + try { + messageDigest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + messageDigest.update(zipFile.getAbsolutePath().getBytes()); + byte[] updatedFileName = messageDigest.digest(zipFile.getAbsolutePath().getBytes()); + + String partFileName = bytesToHex(updatedFileName); + sb.append(partFileName); + sb.append(File.separator); + sb.append(zipEntry.getName()); + return sb.toString(); + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) sb.append(String.format("%02x", b)); + return sb.toString(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLModelReader.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLModelReader.java new file mode 100644 index 0000000..6472d4c --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLModelReader.java @@ -0,0 +1,650 @@ +package de.darmstadt.tu.crossing.parsing; + +import com.google.inject.Injector; +import de.darmstadt.tu.crossing.CrySLStandaloneSetup; +import de.darmstadt.tu.crossing.crySL.AlternativeRequiredPredicates; +import de.darmstadt.tu.crossing.crySL.BuiltinPredicate; +import de.darmstadt.tu.crossing.crySL.ConditionalPredicate; +import de.darmstadt.tu.crossing.crySL.Constraint; +import de.darmstadt.tu.crossing.crySL.ConstraintsBlock; +import de.darmstadt.tu.crossing.crySL.Domainmodel; +import de.darmstadt.tu.crossing.crySL.EnsuresBlock; +import de.darmstadt.tu.crossing.crySL.Event; +import de.darmstadt.tu.crossing.crySL.EventsBlock; +import de.darmstadt.tu.crossing.crySL.ForbiddenBlock; +import de.darmstadt.tu.crossing.crySL.ForbiddenMethod; +import de.darmstadt.tu.crossing.crySL.LabeledMethodCall; +import de.darmstadt.tu.crossing.crySL.Literal; +import de.darmstadt.tu.crossing.crySL.LiteralExpression; +import de.darmstadt.tu.crossing.crySL.LiteralList; +import de.darmstadt.tu.crossing.crySL.NegatesBlock; +import de.darmstadt.tu.crossing.crySL.ObjectExpression; +import de.darmstadt.tu.crossing.crySL.ObjectOperation; +import de.darmstadt.tu.crossing.crySL.ObjectReference; +import de.darmstadt.tu.crossing.crySL.ObjectsBlock; +import de.darmstadt.tu.crossing.crySL.Operator; +import de.darmstadt.tu.crossing.crySL.Order; +import de.darmstadt.tu.crossing.crySL.OrderBlock; +import de.darmstadt.tu.crossing.crySL.Predicate; +import de.darmstadt.tu.crossing.crySL.PredicateParameter; +import de.darmstadt.tu.crossing.crySL.RequiredPredicate; +import de.darmstadt.tu.crossing.crySL.RequiresBlock; +import de.darmstadt.tu.crossing.crySL.ThisPredicateParameter; +import de.darmstadt.tu.crossing.crySL.TimedPredicate; +import de.darmstadt.tu.crossing.crySL.WildcardPredicateParameter; +import de.darmstadt.tu.crossing.rule.CrySLArithmeticConstraint; +import de.darmstadt.tu.crossing.rule.CrySLComparisonConstraint; +import de.darmstadt.tu.crossing.rule.CrySLCondPredicate; +import de.darmstadt.tu.crossing.rule.CrySLConstraint; +import de.darmstadt.tu.crossing.rule.CrySLForbiddenMethod; +import de.darmstadt.tu.crossing.rule.CrySLMethod; +import de.darmstadt.tu.crossing.rule.CrySLObject; +import de.darmstadt.tu.crossing.rule.CrySLPredicate; +import de.darmstadt.tu.crossing.rule.CrySLRule; +import de.darmstadt.tu.crossing.rule.CrySLSplitter; +import de.darmstadt.tu.crossing.rule.CrySLValueConstraint; +import de.darmstadt.tu.crossing.rule.ICrySLPredicateParameter; +import de.darmstadt.tu.crossing.rule.ISLConstraint; +import de.darmstadt.tu.crossing.rule.StateMachineGraph; +import de.darmstadt.tu.crossing.rule.StateNode; +import de.darmstadt.tu.crossing.rule.TransitionEdge; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.common.types.JvmDeclaredType; +import org.eclipse.xtext.common.types.JvmTypeReference; +import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider; +import org.eclipse.xtext.diagnostics.Severity; +import org.eclipse.xtext.resource.XtextResourceSet; +import org.eclipse.xtext.util.CancelIndicator; +import org.eclipse.xtext.validation.CheckMode; +import org.eclipse.xtext.validation.IResourceValidator; +import org.eclipse.xtext.validation.Issue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CrySLModelReader { + + private static final Logger LOGGER = LoggerFactory.getLogger(CrySLModelReader.class); + + private StateMachineGraph smg = null; + private JvmTypeReference currentClass; + private final XtextResourceSet resourceSet; + private final Injector injector; + public static final String cryslFileEnding = ".crysl"; + + private static final String THIS = "this"; + private static final String NULL = "null"; + private static final String UNDERSCORE = "_"; + + /** Creates a CrySLModelReader which creates rules from classes on the run time's classpath. */ + public CrySLModelReader() { + this(CrySLModelReaderClassPath.JAVA_CLASS_PATH); + } + + /** + * Creates a CrySLModelReader which creates rules from classes on the run time's classpath and a + * given virtual classpath. + * + * @param classPath Contains additional classpath elements which are not present on the current + * run time's classpath. + */ + public CrySLModelReader(CrySLModelReaderClassPath classPath) { + CrySLStandaloneSetup crySLStandaloneSetup = new CrySLStandaloneSetup(); + this.injector = crySLStandaloneSetup.createInjectorAndDoEMFRegistration(); + this.resourceSet = injector.getInstance(XtextResourceSet.class); + URL[] classpath = classPath.getClassPath(); + URLClassLoader ucl = new URLClassLoader(classpath); + this.resourceSet.setClasspathURIContext(new URLClassLoader(classpath)); + new ClasspathTypeProvider(ucl, this.resourceSet, null, null); + } + + /** + * Reads the content of a CrySL file from an {@link InputStream}, afterward the {@link + * CrySLRule} will be created. + * + * @param stream the {@link InputStream} holds the CrySL file content + * @param virtualFileName the name needs following structure + * [HexHashedAbsoluteZipFilePath][SystemFileSeparator][ZipEntryName] + * @return the {@link CrySLRule} + * @throws IllegalArgumentException If the file for the rule cannot be found + * @throws IOException If there is a problem with reading the file + * @throws CrySLException If the file is not a .crysl file + */ + public CrySLRule readRule(InputStream stream, String virtualFileName) + throws IOException, CrySLException { + if (!virtualFileName.endsWith(cryslFileEnding)) { + throw new CrySLException( + "The extension of " + virtualFileName + " does not match " + cryslFileEnding); + } + + URI uri = URI.createURI(virtualFileName); + Resource resource = resourceSet.getURIResourceMap().get(uri); + + if (resource == null) { + resource = resourceSet.createResource(uri); + resource.load(stream, Collections.EMPTY_MAP); + } + + return createRuleFromResource(resource); + } + + /** + * Reads the content of a CrySL file and returns a {@link CrySLRule} object. + * + * @param ruleFile the CrySL file + * @return the {@link CrySLRule} object + * @throws CrySLException If the file is not a .crysl file + */ + public CrySLRule readRule(File ruleFile) throws CrySLException { + Resource resource = + resourceSet.getResource(URI.createFileURI(ruleFile.getAbsolutePath()), true); + + return createRuleFromResource(resource); + } + + private boolean runValidator(Resource resource) { + Severity report = Severity.WARNING; + IResourceValidator validator = injector.getInstance(IResourceValidator.class); + Collection issues = + validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl); + boolean errorFound = false; + + for (Issue issue : issues) { + switch (issue.getSeverity()) { + case ERROR: + if (report.compareTo(issue.getSeverity()) >= 0) + LOGGER.error( + "{}:{}: {}", + resource.getURI(), + issue.getLineNumber(), + issue.getMessage()); + errorFound = true; + break; + case WARNING: + if (report.compareTo(issue.getSeverity()) >= 0) + LOGGER.warn( + "{}:{}: {}", + resource.getURI(), + issue.getLineNumber(), + issue.getMessage()); + errorFound = true; + break; + case INFO: + if (report.compareTo(issue.getSeverity()) >= 0) + LOGGER.info( + "{}:{}: {}", + resource.getURI(), + issue.getLineNumber(), + issue.getMessage()); + break; + case IGNORE: + break; + } + } + return errorFound; + } + + private CrySLRule createRuleFromResource(Resource resource) throws CrySLException { + if (resource == null) { + throw new CrySLException( + "Internal error creating a CrySL rule: 'resource parameter was null'."); + } + + if (runValidator(resource)) { + throw new CrySLException( + "Skipping rule since it contains errors: " + resource.getURI()); + } + + try { + return createRuleFromDomainModel((Domainmodel) resource.getContents().get(0)); + } catch (Exception e) { + throw new CrySLException( + "An error occurred while reading the rule " + resource.getURI()); + } + } + + private CrySLRule createRuleFromDomainModel(Domainmodel model) throws CrySLException { + this.currentClass = model.getJavaType(); + String currentClass = this.currentClass.getQualifiedName(); + + if (currentClass.equals("void")) { + throw new CrySLException("Class for the rule is not on the classpath."); + } + + final Collection> objects = getObjects(model.getObjects()); + + Collection forbiddenMethods = + getForbiddenMethods(model.getForbidden()); + + final EventsBlock eventsBlock = model.getEvents(); + final OrderBlock orderBlock = model.getOrder(); + final Collection events = changeDeclaringClass(this.currentClass, eventsBlock); + final Order order = orderBlock == null ? null : orderBlock.getOrder(); + this.smg = StateMachineGraphBuilder.buildSMG(order, events); + + final Collection constraints = new ArrayList<>(); + constraints.addAll(getConstraints(model.getConstraints())); + constraints.addAll(getRequiredPredicates(model.getRequires())); + + // Since 3.0.0: All sections are optional + final Collection eventMethods = new HashSet<>(); + if (eventsBlock != null) { + eventMethods.addAll(CrySLReaderUtils.resolveEventsToCryslMethods(events)); + constraints.addAll(ExceptionsReader.getExceptionConstraints(eventsBlock)); + } + + final EnsuresBlock ensuresBlock = model.getEnsures(); + final NegatesBlock negatesBlock = model.getNegates(); + + final Collection predicates = + new ArrayList<>(getEnsuredPredicates(ensuresBlock)); + final Collection negatedPredicates = + new ArrayList<>(getNegatedPredicates(negatesBlock)); + + return new CrySLRule( + currentClass, + objects, + forbiddenMethods, + eventMethods, + this.smg, + constraints, + predicates, + negatedPredicates); + } + + private Collection changeDeclaringClass( + JvmTypeReference currentClass, EventsBlock eventsBlock) { + if (eventsBlock == null) { + return Collections.emptyList(); + } + + return eventsBlock.getEvents().stream() + .map( + event -> + event instanceof LabeledMethodCall + ? changeDeclaringClass( + currentClass, (LabeledMethodCall) event) + : event) + .collect(Collectors.toList()); + } + + private Event changeDeclaringClass(JvmTypeReference currentClass, LabeledMethodCall event) { + event.getMethod().getMethod().setDeclaringType((JvmDeclaredType) currentClass.getType()); + return event; + } + + private Collection> getObjects(final ObjectsBlock objects) { + if (objects == null) { + return Collections.emptyList(); + } + return objects.getDeclarations().parallelStream() + .map(CrySLReaderUtils::resolveObject) + .collect(Collectors.toList()); + } + + private Collection getForbiddenMethods(final ForbiddenBlock forbidden) { + if (forbidden == null) { + return Collections.emptyList(); + } + + Collection forbiddenMethods = new ArrayList<>(); + + for (final ForbiddenMethod method : forbidden.getForbiddenMethods()) { + CrySLMethod cryslMethod = CrySLReaderUtils.toCrySLMethod(method); + Collection alternatives = + CrySLReaderUtils.resolveEventToCryslMethods(method.getReplacement()); + forbiddenMethods.add(new CrySLForbiddenMethod(cryslMethod, alternatives)); + } + return forbiddenMethods; + } + + private Collection getEnsuredPredicates(final EnsuresBlock ensures) { + if (ensures == null) { + return Collections.emptyList(); + } + + return getTimedPredicates(ensures.getEnsuredPredicates(), false); + } + + private Collection getNegatedPredicates(final NegatesBlock negates) { + if (negates == null) { + return Collections.emptyList(); + } + return getTimedPredicates(negates.getNegatedPredicates(), true); + } + + private Collection getTimedPredicates( + final Collection timedPredicates, boolean negate) { + Collection predicates = new ArrayList<>(timedPredicates.size()); + + for (final TimedPredicate timed : timedPredicates) { + Predicate predicate = timed.getPredicate(); + ISLConstraint constraint = getPredicateCondition(timed); + List parameters = resolvePredicateParameters(predicate); + String name = predicate.getName(); + + if (timed.getAfter() == null) { + predicates.add(new CrySLPredicate(null, name, parameters, negate, constraint)); + } else { + Collection nodes = + getStatesForMethods( + CrySLReaderUtils.resolveEventToCryslMethods(timed.getAfter())); + predicates.add( + new CrySLCondPredicate(null, name, parameters, negate, nodes, constraint)); + } + } + return predicates; + } + + private List resolvePredicateParameters(Predicate predicate) { + final List parameters = + new ArrayList<>(predicate.getParameters().size()); + + for (PredicateParameter parameter : predicate.getParameters()) { + if (parameter instanceof WildcardPredicateParameter) { + parameters.add(new CrySLObject(UNDERSCORE, NULL)); + } else if (parameter instanceof ThisPredicateParameter) { + parameters.add(new CrySLObject(THIS, this.currentClass.getQualifiedName())); + } else { + parameters.add(getObjectExpressionValue(parameter.getValue())); + } + } + return parameters; + } + + private CrySLObject getObjectExpressionValue(ObjectExpression expression) { + if (expression instanceof ObjectReference) { + return getObjectExpressionValue((ObjectReference) expression); + } + if (expression instanceof ObjectOperation) { + return getObjectExpressionValue((ObjectOperation) expression); + } + return null; + } + + private CrySLObject getObjectExpressionValue(ObjectReference reference) { + return CrySLReaderUtils.toCrySLObject(reference.getObject()); + } + + private CrySLObject getObjectExpressionValue(ObjectOperation operation) { + String type = operation.getObject().getType().getQualifiedName(); + String name = operation.getObject().getName(); + switch (operation.getFn()) { + case ALG: + return new CrySLObject(name, type, new CrySLSplitter(0, "/")); + case MODE: + return new CrySLObject(name, type, new CrySLSplitter(1, "/")); + case PAD: + return new CrySLObject(name, type, new CrySLSplitter(2, "/")); + case PART: + int index = Integer.parseInt(operation.getIndex()); + String split = operation.getSplit(); + return new CrySLObject(name, type, new CrySLSplitter(index, split)); + case ELEMENTS: // It does basically nothing + return CrySLReaderUtils.toCrySLObject(operation.getObject()); + default: + return null; + } + } + + private ISLConstraint getPredicateCondition(ConditionalPredicate predicate) { + EObject condition = predicate.getCondition(); + if (condition instanceof Constraint) { + return getConstraint((Constraint) condition); + } + if (condition instanceof Predicate) { + return getPredicate((Predicate) condition); + } + return null; + } + + private CrySLPredicate getPredicate(Predicate predicate) { + return getPredicate(predicate, false, null); + } + + private CrySLPredicate getPredicate( + Predicate predicate, boolean negate, ISLConstraint constraint) { + final List variables = resolvePredicateParameters(predicate); + return new CrySLPredicate(null, predicate.getName(), variables, negate, constraint); + } + + private Collection getRequiredPredicates(RequiresBlock requiresBlock) { + if (requiresBlock == null) { + return Collections.emptyList(); + } + + final Collection predicates = new ArrayList<>(); + final Collection requiredPredicates = + requiresBlock.getRequiredPredicates(); + + for (AlternativeRequiredPredicates alternativePredicates : requiredPredicates) { + List alternatives = + alternativePredicates.getAlternatives().parallelStream() + .map(this::getRequiredPredicate) + .collect(Collectors.toList()); + ISLConstraint predicate = alternatives.get(0); + + for (int i = 1; i < alternatives.size(); i++) + predicate = + new CrySLConstraint( + alternatives.get(i), predicate, CrySLConstraint.LogOps.or); + predicates.add(predicate); + } + return predicates; + } + + private CrySLPredicate getRequiredPredicate(RequiredPredicate predicate) { + ISLConstraint constraint = getPredicateCondition(predicate); + boolean negate = predicate.isNegated(); + return getPredicate(predicate.getPredicate(), negate, constraint); + } + + private Collection getConstraints(ConstraintsBlock constraintsBlock) { + if (constraintsBlock == null) { + return Collections.emptyList(); + } + return constraintsBlock.getConstraints().parallelStream() + .map(this::getConstraint) + .collect(Collectors.toList()); + } + + private ISLConstraint getConstraint(final Constraint constraint) { + + if (constraint instanceof LiteralExpression) { + return getLiteralExpression((LiteralExpression) constraint); + } + + switch (constraint.getOp()) { + /* Logical Expressions */ + case NOT: + // NOT operator was only implemented for Predicates, which were + // not reachable from the Constraint rule. + // Add it to LogOps? + throw new UnsupportedOperationException("The NOT operator is not implemented."); + case IMPLY: + case OR: + case AND: + { + ISLConstraint left = getConstraint(constraint.getLeft()); + ISLConstraint right = getConstraint(constraint.getRight()); + CrySLConstraint.LogOps logOp = + CrySLReaderUtils.logOpFromOperator(constraint.getOp()).get(); + return new CrySLConstraint(left, right, logOp); + } + /* Comparison Expressions */ + case EQUAL: // LogOps specifies eq as well, but it was not used + case UNEQUAL: + case GREATER: + case GREATER_OR_EQUAL: + case LESS: + case LESS_OR_EQUAL: + { + CrySLComparisonConstraint.CompOp compOp = + CrySLReaderUtils.compOpFromOperator(constraint.getOp()).get(); + CrySLArithmeticConstraint left = + coerceConstraintToArithmeticConstraint( + getConstraint(constraint.getLeft())); + CrySLArithmeticConstraint right = + coerceConstraintToArithmeticConstraint( + getConstraint(constraint.getRight())); + return new CrySLComparisonConstraint(left, right, compOp); + } + /* Arithmetic Expressions */ + case TIMES: + case DIVIDE: + // These were specified in Syntax, but not implemented here. + // Add it to ArithOp? + throw new UnsupportedOperationException( + "The multiplication operators are not implemented."); + case PLUS: + case MINUS: + case MODULO: + { + ISLConstraint left = getConstraint(constraint.getLeft()); + ISLConstraint right = getConstraint(constraint.getRight()); + CrySLArithmeticConstraint.ArithOp arithOp = + CrySLReaderUtils.arithOpFromOperator(constraint.getOp()).get(); + return new CrySLArithmeticConstraint(left, right, arithOp); + } + /* In Expression */ + case IN: + { + CrySLObject left = + constraint.getLeft() instanceof ObjectExpression + ? getObjectExpressionValue( + (ObjectExpression) constraint.getLeft()) + : constraint.getLeft() instanceof Literal + ? CrySLReaderUtils.toCrySLObject( + (Literal) constraint.getLeft()) + : null; + if (left == null) + throw new IllegalArgumentException( + "lhs of an IN expression must be an Object or an Operation thereon."); + LiteralList right = (LiteralList) constraint.getRight(); + List values = + right.getElements().stream() + .map(Literal::getValue) + .collect(Collectors.toList()); + return new CrySLValueConstraint(left, values); + } + } + return null; + } + + private CrySLArithmeticConstraint coerceConstraintToArithmeticConstraint( + ISLConstraint constraint) { + if (constraint instanceof CrySLArithmeticConstraint) { + return (CrySLArithmeticConstraint) constraint; + } + if (constraint instanceof CrySLPredicate) { + return makeArithmeticConstraint(constraint); + } + throw new ClassCastException( + "Cant coerce `" + constraint.toString() + "` into ArithmeticExpression"); + } + + private ISLConstraint getLiteralExpression(LiteralExpression expression) { + if (expression instanceof BuiltinPredicate) { + return getBuiltinPredicate((BuiltinPredicate) expression); + } + if (expression instanceof Literal) { + return makeConstraintFromObject(CrySLReaderUtils.toCrySLObject((Literal) expression)); + } + if (expression instanceof ObjectExpression) { + return makeConstraintFromObject( + getObjectExpressionValue((ObjectExpression) expression)); + } + return null; + } + + /** This is weird, but is taken from the original implementation. */ + private ISLConstraint makeConstraintFromObject(ICrySLPredicateParameter object) { + return makeArithmeticConstraint(object); + } + + private CrySLArithmeticConstraint makeArithmeticConstraint(ICrySLPredicateParameter object) { + CrySLObject zero = new CrySLObject("0", "int"); + CrySLArithmeticConstraint.ArithOp plus = + CrySLReaderUtils.arithOpFromOperator(Operator.PLUS).get(); + return new CrySLArithmeticConstraint(object, zero, plus); + } + + private Collection getStatesForMethods(final Collection condition) { + final Collection predicateGenerationNodes = new HashSet<>(); + if (condition.isEmpty()) { + return predicateGenerationNodes; + } + + for (final TransitionEdge transition : this.smg.getAllTransitions()) { + if (transition.getLabel().containsAll(condition)) { + Collection reachableNodes = + getAllReachableNodes(transition.getRight(), new HashSet<>()); + + predicateGenerationNodes.addAll(reachableNodes); + } + } + return predicateGenerationNodes; + } + + private Collection getAllReachableNodes( + final StateNode startNode, Collection visited) { + if (visited.contains(startNode)) { + return visited; + } + + visited.add(startNode); + + for (TransitionEdge edge : this.smg.getAllOutgoingEdges(startNode)) { + Collection reachableNodes = getAllReachableNodes(edge.getRight(), visited); + + visited.addAll(reachableNodes); + } + return visited; + } + + private ISLConstraint getBuiltinPredicate(BuiltinPredicate builtinPredicate) { + String name = builtinPredicate.getPredicate().getLiteral(); + List parameters; + boolean negated = false; + + switch (builtinPredicate.getPredicate()) { + case NO_CALL_TO: + case CALL_TO: + parameters = + CrySLReaderUtils.resolveEventToPredicateParameters( + builtinPredicate.getEvent()); + break; + + case INSTANCE_OF: + case NEVER_TYPE_OF: + parameters = + List.of( + CrySLReaderUtils.toCrySLObject(builtinPredicate.getObject()), + new CrySLObject( + NULL, builtinPredicate.getType().getQualifiedName())); + break; + + case NOT_HARD_CODED: + case LENGTH: + CrySLObject object = CrySLReaderUtils.toCrySLObject(builtinPredicate.getObject()); + parameters = Collections.singletonList(object); + break; + default: + parameters = Collections.emptyList(); + } + return new CrySLPredicate(null, name, parameters, negated); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLModelReaderClassPath.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLModelReaderClassPath.java new file mode 100644 index 0000000..c3d0e22 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLModelReaderClassPath.java @@ -0,0 +1,135 @@ +package de.darmstadt.tu.crossing.parsing; + +import com.google.common.base.Splitter; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Appendable, virtual classpath extension, allowing to add custom elements, even after the actual + * classpath was already set. + */ +public class CrySLModelReaderClassPath { + + private static final Logger LOGGER = LoggerFactory.getLogger(CrySLModelReaderClassPath.class); + + private static final URL[] javaRuntimeClassPath; + private final Collection virtualClassPath; + + public static final CrySLModelReaderClassPath JAVA_CLASS_PATH = new CrySLModelReaderClassPath(); + + static { + Collection runtimeClassPath = + Splitter.on(File.pathSeparatorChar) + .splitToList(System.getProperty("java.class.path")); + javaRuntimeClassPath = + runtimeClassPath.stream() + .map( + (it) -> { + try { + return new File(it).toURI().toURL(); + } catch (MalformedURLException e) { + LOGGER.warn( + "Unable to get URL from java classpath element '" + + it + + "'.", + e); + return null; + } + }) + .filter(Objects::nonNull) + .toArray(URL[]::new); + } + + /** Initializes a new instance with the current run time's classpath. */ + private CrySLModelReaderClassPath() { + virtualClassPath = Collections.emptySet(); + } + + /** + * Initializes a new instance with the current run time's classpath and the elements the given + * set. + * + * @param virtualClassPath the virtual class path + */ + public CrySLModelReaderClassPath(Collection virtualClassPath) { + this.virtualClassPath = virtualClassPath; + } + + /** + * Creates a new instance with the current run time's classpath and the elements the given set. + * + * @param virtualClassPath the virtual class path + * @return the model reader class path instance for the given virtual class path + */ + public static CrySLModelReaderClassPath createFromPaths(Collection virtualClassPath) { + Collection urlClassPath = + virtualClassPath.stream() + .map( + (it) -> { + try { + return it.toAbsolutePath().toUri().toURL(); + } catch (MalformedURLException e) { + LOGGER.warn( + "Unable to get URL from virtual classpath element '" + + it.toAbsolutePath() + + "'.", + e); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + return new CrySLModelReaderClassPath(urlClassPath); + } + + /** + * Creates a new instance with the current run time's classpath and the elements the given set. + * + * @param virtualClassPath the virtual class path + * @return the model reader class path instance for the given virtual class path + */ + public static CrySLModelReaderClassPath createFromURIs(Collection virtualClassPath) { + Collection urlClassPath = + virtualClassPath.stream() + .map( + (it) -> { + try { + return it.toURL(); + } catch (MalformedURLException e) { + LOGGER.warn( + "Unable to get URL from virtual classpath element '" + + it + + "'.", + e); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + return new CrySLModelReaderClassPath(urlClassPath); + } + + /** + * Get the class path. + * + * @return A copy of the current state of the classpath. + */ + public URL[] getClassPath() { + if (virtualClassPath.isEmpty()) return javaRuntimeClassPath.clone(); + Collection classPath = new HashSet<>(); + classPath.addAll(Arrays.asList(javaRuntimeClassPath)); + classPath.addAll(virtualClassPath); + return classPath.toArray(new URL[0]); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLReaderUtils.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLReaderUtils.java new file mode 100644 index 0000000..9673c3a --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CrySLReaderUtils.java @@ -0,0 +1,217 @@ +package de.darmstadt.tu.crossing.parsing; + +import de.darmstadt.tu.crossing.crySL.Aggregate; +import de.darmstadt.tu.crossing.crySL.AnyParameterType; +import de.darmstadt.tu.crossing.crySL.BooleanLiteral; +import de.darmstadt.tu.crossing.crySL.Constraint; +import de.darmstadt.tu.crossing.crySL.Event; +import de.darmstadt.tu.crossing.crySL.Exception; +import de.darmstadt.tu.crossing.crySL.ExceptionAggregate; +import de.darmstadt.tu.crossing.crySL.ExceptionDeclaration; +import de.darmstadt.tu.crossing.crySL.ForbiddenMethod; +import de.darmstadt.tu.crossing.crySL.IntLiteral; +import de.darmstadt.tu.crossing.crySL.LabeledMethodCall; +import de.darmstadt.tu.crossing.crySL.Literal; +import de.darmstadt.tu.crossing.crySL.Method; +import de.darmstadt.tu.crossing.crySL.Object; +import de.darmstadt.tu.crossing.crySL.Operator; +import de.darmstadt.tu.crossing.crySL.StringLiteral; +import de.darmstadt.tu.crossing.rule.CrySLArithmeticConstraint; +import de.darmstadt.tu.crossing.rule.CrySLComparisonConstraint; +import de.darmstadt.tu.crossing.rule.CrySLConstraint; +import de.darmstadt.tu.crossing.rule.CrySLException; +import de.darmstadt.tu.crossing.rule.CrySLMethod; +import de.darmstadt.tu.crossing.rule.CrySLObject; +import de.darmstadt.tu.crossing.rule.ICrySLPredicateParameter; +import java.util.AbstractMap.SimpleEntry; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.eclipse.xtext.common.types.JvmTypeParameter; + +public class CrySLReaderUtils { + + public static Collection resolveEventToCryslMethods(final Event event) { + return resolveEventToCryslMethodsStream(event).collect(Collectors.toList()); + } + + public static List resolveEventToPredicateParameters( + final Event event) { + return resolveEventToCryslMethodsStream(event).collect(Collectors.toList()); + } + + public static Collection resolveEventsToCryslMethods( + final Collection events) { + return resolveEventsToCryslMethodsStream(events).collect(Collectors.toList()); + } + + public static Stream resolveEventsToCryslMethodsStream( + final Collection events) { + return events.parallelStream().flatMap(CrySLReaderUtils::resolveEventToCryslMethodsStream); + } + + public static Stream resolveEventToCryslMethodsStream(final Event event) { + if (event instanceof Aggregate) return resolveEventToCryslMethodsStream((Aggregate) event); + if (event instanceof LabeledMethodCall) + return resolveEventToCryslMethodsStream((LabeledMethodCall) event); + return Stream.empty(); + } + + protected static Stream resolveEventToCryslMethodsStream( + final Aggregate aggregate) { + return aggregate.getEvents().parallelStream() + .flatMap(CrySLReaderUtils::resolveEventToCryslMethodsStream); + } + + protected static Stream resolveEventToCryslMethodsStream( + final LabeledMethodCall event) { + return Stream.of(toCrySLMethod((event.getMethod()))); + } + + public static CrySLMethod toCrySLMethod(final ForbiddenMethod method) { + String name = method.getMethod().getQualifiedName(); + String declaringClassType = method.getMethod().getDeclaringType().getQualifiedName(); + List> parameters = + method.getParameters().stream() + .map( + parameter -> + new SimpleEntry<>( + parameter.getSimpleName(), + parameter.getType().getQualifiedName())) + .collect(Collectors.toList()); + return new CrySLMethod(name, declaringClassType, parameters, resolveObject(null)); + } + + public static CrySLMethod toCrySLMethod(final Method method) { + String name = method.getMethod().getQualifiedName(); + String declaringClassType = method.getMethod().getDeclaringType().getQualifiedName(); + List> parameters = + method.getParameters().stream() + .map( + parameter -> + parameter instanceof AnyParameterType + ? new SimpleEntry<>( + CrySLMethod.NO_NAME, CrySLMethod.ANY_TYPE) + : resolveObject((parameter.getValue()))) + .collect(Collectors.toList()); + return new CrySLMethod( + name, declaringClassType, parameters, resolveObject(method.getReturn())); + } + + public static CrySLObject toCrySLObject(Object object) { + return new CrySLObject(object.getName(), object.getType().getQualifiedName()); + } + + public static CrySLObject toCrySLObject(Literal literal) { + String value = literal.getValue(); + String type = + literal instanceof IntLiteral + ? "int" + : literal instanceof BooleanLiteral + ? "boolean" + : literal instanceof StringLiteral + ? String.class.getName() + : "void"; + return new CrySLObject(value, type); + } + + public static Stream resolveExceptionsStream(final Exception exception) { + if (exception instanceof ExceptionDeclaration) + return resolveExceptionsStream((ExceptionDeclaration) exception); + if (exception instanceof ExceptionAggregate) + return resolveExceptionsStream((ExceptionAggregate) exception); + return Stream.empty(); + } + + protected static Stream resolveExceptionsStream( + final ExceptionAggregate exception) { + return exception.getExceptions().stream() + .flatMap(CrySLReaderUtils::resolveExceptionsStream); + } + + protected static Stream resolveExceptionsStream( + final ExceptionDeclaration exception) { + return Stream.of(toCrySLException(exception)); + } + + public static CrySLException toCrySLException(final ExceptionDeclaration exception) { + return new CrySLException(exception.getException().getIdentifier()); + } + + public static Map.Entry resolveObject(final Object o) { + if (o == null) { + return new SimpleEntry<>(CrySLMethod.NO_NAME, CrySLMethod.VOID); + } + + if (o.getType().getType() instanceof JvmTypeParameter) { + return new SimpleEntry<>(o.getName(), "java.lang.Object"); + } else { + return new SimpleEntry<>(o.getName(), o.getType().getQualifiedName()); + } + } + + public static Optional arithOpFromOperator( + Operator operator) { + switch (operator) { + case PLUS: + return Optional.of(CrySLArithmeticConstraint.ArithOp.p); + // case TIMES: return Optional.of(ArithOp.t); /* Only in Syntax yet */ + // case DIVIDE: return Optional.of(ArithOp.g); /* Only in Syntax yet */ + case MINUS: + return Optional.of(CrySLArithmeticConstraint.ArithOp.n); + case MODULO: + return Optional.of(CrySLArithmeticConstraint.ArithOp.m); + default: + return Optional.empty(); + } + } + + public static Optional logOpFromOperator(Operator operator) { + switch (operator) { + case AND: + return Optional.of(CrySLConstraint.LogOps.and); + case OR: + return Optional.of(CrySLConstraint.LogOps.or); + case IMPLY: + return Optional.of(CrySLConstraint.LogOps.implies); + // case NOT: return Optional.of(LogOps.not); /* Only in Syntax yet */ + // case EQUAL: return Optional.of(LogOps.eq); /* unused enum item */ + default: + return Optional.empty(); + } + } + + public static Optional compOpFromOperator(Operator operator) { + switch (operator) { + case EQUAL: + return Optional.of(CrySLComparisonConstraint.CompOp.eq); + case UNEQUAL: + return Optional.of(CrySLComparisonConstraint.CompOp.neq); + case LESS: + return Optional.of(CrySLComparisonConstraint.CompOp.l); + case LESS_OR_EQUAL: + return Optional.of(CrySLComparisonConstraint.CompOp.le); + case GREATER: + return Optional.of(CrySLComparisonConstraint.CompOp.g); + case GREATER_OR_EQUAL: + return Optional.of(CrySLComparisonConstraint.CompOp.ge); + default: + return Optional.empty(); + } + } + + public static boolean isArithmeticExpression(Constraint constraint) { + return arithOpFromOperator(constraint.getOp()).isPresent(); + } + + public static boolean isLogicExpression(Constraint constraint) { + return logOpFromOperator(constraint.getOp()).isPresent(); + } + + public static boolean isComparisonExpression(Constraint constraint) { + return compOpFromOperator(constraint.getOp()).isPresent(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CryslException.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CryslException.java new file mode 100644 index 0000000..39dfcb6 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/CryslException.java @@ -0,0 +1,8 @@ +package de.darmstadt.tu.crossing.parsing; + +public class CrySLException extends Exception { + + public CrySLException(String message) { + super(message); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/ExceptionsReader.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/ExceptionsReader.java new file mode 100644 index 0000000..e90f01e --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/ExceptionsReader.java @@ -0,0 +1,30 @@ +package de.darmstadt.tu.crossing.parsing; + +import de.darmstadt.tu.crossing.crySL.EventsBlock; +import de.darmstadt.tu.crossing.crySL.LabeledMethodCall; +import de.darmstadt.tu.crossing.rule.CrySLExceptionConstraint; +import de.darmstadt.tu.crossing.rule.CrySLMethod; +import java.util.Collection; +import java.util.stream.Collectors; + +/** Helper class to derive {@link CrySLExceptionConstraint}'s from the events. */ +public abstract class ExceptionsReader { + public static Collection getExceptionConstraints( + EventsBlock eventsBlock) { + return eventsBlock.getEvents().stream() + .filter(event -> event instanceof LabeledMethodCall) + .map(event -> (LabeledMethodCall) event) + .flatMap( + meth -> + CrySLReaderUtils.resolveExceptionsStream(meth.getException()) + .map( + exception -> { + CrySLMethod method = + CrySLReaderUtils.toCrySLMethod( + meth.getMethod()); + return new CrySLExceptionConstraint( + method, exception); + })) + .collect(Collectors.toList()); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/StateMachineGraphBuilder.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/StateMachineGraphBuilder.java new file mode 100644 index 0000000..4d145d5 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/parsing/StateMachineGraphBuilder.java @@ -0,0 +1,258 @@ +package de.darmstadt.tu.crossing.parsing; + +import de.darmstadt.tu.crossing.crySL.Event; +import de.darmstadt.tu.crossing.crySL.Order; +import de.darmstadt.tu.crossing.crySL.OrderOperator; +import de.darmstadt.tu.crossing.crySL.Primary; +import de.darmstadt.tu.crossing.rule.CrySLMethod; +import de.darmstadt.tu.crossing.rule.FiniteStateMachine; +import de.darmstadt.tu.crossing.rule.StateMachineGraph; +import de.darmstadt.tu.crossing.rule.StateNode; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +/** + * This class will build a {@link FiniteStateMachine} for a given ORDER expression from crysl rules. + * + * @author marvinvogel + */ +public class StateMachineGraphBuilder { + + private final Order order; + private final StateMachineGraph result; + private final Collection events; + private final Collection allMethods = new HashSet<>(); + + public static StateMachineGraph buildSMG(final Order order, final Collection events) { + return (new StateMachineGraphBuilder(order, events)).buildSMG(); + } + + protected StateMachineGraphBuilder(final Order order, final Collection events) { + this.order = order; + this.events = events; + this.result = new StateMachineGraph(); + } + + protected StateMachineGraph buildSMG() { + StateNode initialNode = new StateNode("-1", true, false); + this.result.addNode(initialNode); + SubStateMachine subSmg = buildSubSMG(this.order, Collections.singleton(initialNode)); + subSmg.getEndNodes().parallelStream().forEach(StateNode::makeAccepting); + return this.result; + } + + /** Helper class to store a {@link Collection} of endNodes and startNodes. */ + private static class SubStateMachine { + + private final Collection startNodes; + private final Collection endNodes; + + public SubStateMachine(final StateNode startNode, final StateNode endNode) { + this(Collections.singleton(startNode), Collections.singleton(endNode)); + } + + public SubStateMachine( + final Collection startNodes, final Collection endNodes) { + this.startNodes = startNodes; + this.endNodes = endNodes; + } + + public Collection getStartNodes() { + return this.startNodes; + } + + public Collection getEndNodes() { + return this.endNodes; + } + } + + /** + * This method builds a {@link SubStateMachine} equivalent to the given {@link Order}. + * + *

It is called recursively For a collection of start nodes, which must already be added to + * the {@link StateMachineGraph} result. + * + * @param order The CrySL {@link Order} Expression to build the {@link SubStateMachine} for. + * @param startNodes Set of nodes used as the startNodes of this {@link SubStateMachine}. + * @return the {@link SubStateMachine} representing this {@link Order} Expression + *

Having a look at the Crysl Xtext definition for the Order section, we have ----------- + * + * Sequence returns Order: + * Alternative ({Order.left=current} op=SequenceOperator right=Alternative)* + * ; + *

+ * enum SequenceOperator returns OrderOperator: + * SEQUENCE = ',' + * ; + *

+ *

+ * Alternative returns Order: + * Cardinality ({Order.left=current} op=AlternativeOperator right=Cardinality)* + * ; + *

+ *

+ * enum AlternativeOperator returns OrderOperator: + * ALTERNATIVE = '|' + * ; + *

+ *

+ * Cardinality returns Order: + * Primary ({Order.left=current} op = CardinalityOperator)? + * ; + *

+ *

+ * enum CardinalityOperator returns OrderOperator: + * ZERO_OR_MORE = '*' | ONE_OR_MORE = '+' | ZERO_OR_ONE = '?' + * ; + *

+ *

+ * Primary returns Order: + * {Primary} event = [Event] + * | '(' Order ')' + * ; + *

+ *
----------- + *

Based on this definition, the method will build the StateMachine from the Order + * section. + *

This is done by recursively building a sub-StateMachine and connecting it with given + * start Nodes and returned end Nodes according to the specific OrderOperator. + *

Therefore, consider the following cases + *

1. Order instanceof Primary: In this case, we create a new Node and add a transition + * for the Event from each start Node to the newly created Node. Finally, we return a + * SubStateMachine where the newly created node is the start and end Node. + *

2. OrderOperator == SEQUENCE: The left-side should occur before the right-side. We + * therefore recursively build the sub-StateMachine of the left-side with the given start + * Nodes saving the returned end Nodes. We then build the sub-StateMachine of the right-side + * giving it the left-side's end Nodes as start Nodes. Finally, we return the startNodes of + * the left-side's start Nodes as our start Nodes and the end Nodes of the right-side's + * sub-StateMachine as our end Nodes. + *

3. OrderOperator == ALTERNATIVE: Either the left-side or the right-side should occur. + * We therefore build both sub-StateMachines with our start Nodes as start Nodes. Finally, + * we return the aggregate of both start Nodes as our startNodes and the aggregates of both + * end Nodes as our end Nodes. + *

4. OrderOperator == ZERO_OR_ONE: The Event can occur or be skipped. We therefore build + * the sub-StateMachine (only the left-side is present) with our start Nodes as start Nodes. + * Finally, we return the returned start Nodes as our start Nodes and the returned Nodes end + * Nodes (event occurs) and our start nodes (event skipped) as end Nodes. + *

5. OrderOperator == ONE_OR_MORE: The Event can occur once or multiple times. We + * therefore build the sub-StateMachine (only the left-side is present) with our start Nodes + * as start Nodes and save the returned end Nodes. We then duplicate the transitions from + * each given start Node to the start node of the Sub-StateMachine, but not with the given + * start Node as origin, but each end Node of the Sub-StateMachine, this creates the desired + * loop. Finally, we return the returned start and end Nodes. + *

5. OrderOperator == ZERO_OR_MORE: This can be seen as (Order)*?. We therefore proceed + * as in ONE_OR_MORE but additionally return the given start Nodes as end Nodes as well as + * in ZERO_OR_ONE. + */ + private SubStateMachine buildSubSMG(final Order order, final Collection startNodes) { + + if (order == null) { + // This is the case if the ORDER section was omitted. + // It implies, that any method may be called in any sequence. + // We therefore create a Node and a transition from all startNodes + // to this node and a loop from the node to itself. + StateNode node = this.result.createNewNode(); + node.setAccepting(true); + Collection label = new HashSet<>(retrieveAllMethodsFromEvents()); + + for (StateNode startNode : startNodes) { + this.result.createNewEdge(label, startNode, node); + } + + this.result.createNewEdge(label, node, node); + return new SubStateMachine(Collections.singleton(node), startNodes); + } + + if (order instanceof Primary) { + Event event = ((Primary) order).getEvent(); + StateNode node = this.result.createNewNode(); + Collection label = CrySLReaderUtils.resolveEventToCryslMethods(event); + + for (StateNode startNode : startNodes) { + this.result.createNewEdge(label, startNode, node); + } + + return new SubStateMachine(node, node); + } + + Collection end = new HashSet<>(); + Collection start = new HashSet<>(); + + final SubStateMachine left; + final SubStateMachine right; + + switch (order.getOp()) { + case SEQUENCE: + left = buildSubSMG(order.getLeft(), startNodes); + right = buildSubSMG(order.getRight(), left.getEndNodes()); + start.addAll(left.getStartNodes()); + end.addAll(right.getEndNodes()); + + for (StateNode node : startNodes) { + if (left.getEndNodes().contains(node)) { + start.addAll(right.getStartNodes()); + } + } + + break; + case ALTERNATIVE: + left = buildSubSMG(order.getLeft(), startNodes); + right = buildSubSMG(order.getRight(), startNodes); + start.addAll(left.getStartNodes()); + start.addAll(right.getStartNodes()); + end.addAll(left.getEndNodes()); + end.addAll(right.getEndNodes()); + // TODO For some reason, this part removes loops in accepting states + /* reduce all end nodes without outgoing edges to one end node + * Set endNodesWithOutgoingEdges = + * this.result.getEdges().parallelStream() + * .map(edge -> edge.from()).filter(node -> + * end.contains(node)).collect(Collectors.toSet()); + * if (endNodesWithOutgoingEdges.size() < end.size() - 1) { + * end.removeAll(endNodesWithOutgoingEdges); + * StateNode aggrNode = this.result.aggregateNodesToOneNode(end, + * end.iterator().next()); + * end.clear(); + * end.add(aggrNode); + * end.addAll(endNodesWithOutgoingEdges); + * } + */ + break; + case ONE_OR_MORE: + case ZERO_OR_MORE: + case ZERO_OR_ONE: + left = buildSubSMG(order.getLeft(), startNodes); + start.addAll(left.getStartNodes()); + end.addAll(left.getEndNodes()); + if (order.getOp() == OrderOperator.ZERO_OR_MORE + || order.getOp() == OrderOperator.ONE_OR_MORE) { + startNodes.stream() + .map(this.result::getAllOutgoingEdges) + .flatMap(Collection::stream) + .filter(edge -> left.getStartNodes().contains(edge.getRight())) + .forEach( + edge -> + left.getEndNodes() + .forEach( + endNode -> + this.result.createNewEdge( + edge.getLabel(), + endNode, + edge.getRight()))); + } + if (order.getOp() == OrderOperator.ZERO_OR_MORE + || order.getOp() == OrderOperator.ZERO_OR_ONE) { + end.addAll(startNodes); + } + break; + } + return new SubStateMachine(start, end); + } + + private Collection retrieveAllMethodsFromEvents() { + if (this.allMethods.isEmpty()) + this.allMethods.addAll(CrySLReaderUtils.resolveEventsToCryslMethods(this.events)); + return this.allMethods; + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLArithmeticConstraint.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLArithmeticConstraint.java new file mode 100644 index 0000000..ab27d82 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLArithmeticConstraint.java @@ -0,0 +1,87 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.ArrayList; +import java.util.List; + +public class CrySLArithmeticConstraint extends CrySLLiteral { + + public enum ArithOp { + p, + n, + m + } + + /* p = + + * n = - + * m = % + */ + + private final ArithOp operator; + private final ICrySLPredicateParameter left; + private final ICrySLPredicateParameter right; + + public CrySLArithmeticConstraint( + ICrySLPredicateParameter l, ICrySLPredicateParameter r, ArithOp op) { + left = l; + right = r; + operator = op; + } + + /** + * @return the operator + */ + public ArithOp getOperator() { + return operator; + } + + /** + * @return the left + */ + public ICrySLPredicateParameter getLeft() { + return left; + } + + /** + * @return the right + */ + public ICrySLPredicateParameter getRight() { + return right; + } + + public String toString() { + return left + + " " + + (operator.equals(ArithOp.p) ? "+" : (operator.equals(ArithOp.m) ? "%" : "-")) + + " " + + right; + } + + @Override + public List getInvolvedVarNames() { + List varNames = new ArrayList<>(); + String name = left.getName(); + if (!isIntOrBoolean(name)) { + varNames.add(name); + } + + name = right.getName(); + if (!isIntOrBoolean(name)) { + varNames.add(name); + } + return varNames; + } + + private boolean isIntOrBoolean(String name) { + try { + Integer.parseInt(name); + return true; + } catch (NumberFormatException ex) { + return name.equalsIgnoreCase("false") || name.equalsIgnoreCase("true"); + } + } + + @Override + public String getName() { + return toString(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLComparisonConstraint.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLComparisonConstraint.java new file mode 100644 index 0000000..9b550b8 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLComparisonConstraint.java @@ -0,0 +1,80 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.List; + +public class CrySLComparisonConstraint extends CrySLLiteral { + + public enum CompOp { + l, + g, + le, + ge, + eq, + neq + } + + private final CompOp operator; + private final CrySLArithmeticConstraint left; + private final CrySLArithmeticConstraint right; + + public CrySLComparisonConstraint( + CrySLArithmeticConstraint l, CrySLArithmeticConstraint r, CompOp op) { + left = l; + right = r; + operator = op; + } + + public String toString() { + return left + " " + getOperatorString() + " " + right; + } + + private String getOperatorString() { + switch (operator) { + case l: + return "<"; + case le: + return "<="; + case g: + return ">"; + case ge: + return ">="; + case neq: + return "!="; + default: + return "="; + } + } + + /** + * @return the operator + */ + public CompOp getOperator() { + return operator; + } + + /** + * @return the left + */ + public CrySLArithmeticConstraint getLeft() { + return left; + } + + /** + * @return the right + */ + public CrySLArithmeticConstraint getRight() { + return right; + } + + @Override + public List getInvolvedVarNames() { + List varNames = left.getInvolvedVarNames(); + varNames.addAll(right.getInvolvedVarNames()); + return varNames; + } + + @Override + public String getName() { + return toString(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLCondPredicate.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLCondPredicate.java new file mode 100644 index 0000000..d49dfdc --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLCondPredicate.java @@ -0,0 +1,52 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Collection; +import java.util.List; + +public class CrySLCondPredicate extends CrySLPredicate { + + private final Collection conditionalNodes; + + public CrySLCondPredicate( + ICrySLPredicateParameter baseObj, + String name, + List parameters, + Boolean negated, + Collection nodes) { + this(baseObj, name, parameters, negated, nodes, null); + } + + public CrySLCondPredicate( + ICrySLPredicateParameter baseObj, + String name, + List parameters, + Boolean negated, + Collection nodes, + ISLConstraint constraint) { + super(baseObj, name, parameters, negated, constraint); + this.conditionalNodes = nodes; + } + + /** + * @return the conditionalMethods + */ + public Collection getConditionalMethods() { + return conditionalNodes; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) return false; + + if (!(obj instanceof CrySLCondPredicate)) return false; + + CrySLCondPredicate other = (CrySLCondPredicate) obj; + if (!getConditionalMethods().equals(other.getConditionalMethods())) return false; + + return true; + } + + public String toString() { + return super.toString() + " after " + conditionalNodes; + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLConstraint.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLConstraint.java new file mode 100644 index 0000000..e950257 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLConstraint.java @@ -0,0 +1,64 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.List; + +public class CrySLConstraint implements ISLConstraint { + + public enum LogOps { + and, + or, + implies, + eq + } + + private final LogOps operator; + private final ISLConstraint left; + private final ISLConstraint right; + + public CrySLConstraint(ISLConstraint l, ISLConstraint r, LogOps op) { + left = l; + right = r; + operator = op; + } + + /** + * @return the operator return operator; + */ + public LogOps getOperator() { + return operator; + } + + /** + * @return the left + */ + public ISLConstraint getLeft() { + return left; + } + + /** + * @return the right + */ + public ISLConstraint getRight() { + return right; + } + + public String toString() { + StringBuilder constraintSB = new StringBuilder(); + constraintSB.append(left.toString()); + constraintSB.append(operator); + constraintSB.append(right.toString()); + return constraintSB.toString(); + } + + @Override + public List getInvolvedVarNames() { + List varNames = left.getInvolvedVarNames(); + varNames.addAll(right.getInvolvedVarNames()); + return varNames; + } + + @Override + public String getName() { + return toString(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLException.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLException.java new file mode 100644 index 0000000..29fdd28 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLException.java @@ -0,0 +1,28 @@ +package de.darmstadt.tu.crossing.rule; + +/** Helper Class to store an {@link Exception} as a String. */ +public class CrySLException { + + private final String exception; + + /** + * Construct a {@link CrySLException} from the fully qualified classname of the {@link + * Exception} to store. + * + * @param exception the exception's name + */ + public CrySLException(String exception) { + this.exception = exception; + } + + /** + * @return The fully qualified classname of the stored {@link Exception}. + */ + public String getException() { + return exception; + } + + public String toString() { + return String.format("%s(%s)", this.getClass().getName(), this.exception); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLExceptionConstraint.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLExceptionConstraint.java new file mode 100644 index 0000000..d1114ca --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLExceptionConstraint.java @@ -0,0 +1,61 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Collections; +import java.util.List; + +/** + * Constraint expressing, that a {@link CrySLMethod} throws an {@link CrySLException}, that must be + * caught. + */ +public class CrySLExceptionConstraint implements ISLConstraint { + + /** The Method throwing the Exception. */ + private final CrySLMethod method; + + /** The Exception thrown by the Method. */ + private final CrySLException exception; + + /** + * Construct the {@link CrySLExceptionConstraint} given the method and the exception thrown + * thereby. + * + * @param method Method that throws the Exception. + * @param exception Exception that thrown by the Method. + */ + public CrySLExceptionConstraint(CrySLMethod method, CrySLException exception) { + this.method = method; + this.exception = exception; + } + + /** + * Returns the Method throwing the Exception. + * + * @return The Method throwing the Exception. + */ + public CrySLMethod getMethod() { + return method; + } + + /** + * Returns the Exception thrown by the Exception. + * + * @return The Exception thrown by the Exception. + */ + public CrySLException getException() { + return exception; + } + + public String toString() { + return String.format("%s(%s, %s)", this.getClass().getName(), getMethod(), getException()); + } + + @Override + public List getInvolvedVarNames() { + return Collections.emptyList(); + } + + @Override + public String getName() { + return toString(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLForbiddenMethod.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLForbiddenMethod.java new file mode 100644 index 0000000..4ed8103 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLForbiddenMethod.java @@ -0,0 +1,55 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Collection; + +public class CrySLForbiddenMethod { + + private final CrySLMethod method; + private final Collection alternatives; + + public CrySLForbiddenMethod(CrySLMethod method, Collection alternatives) { + this.method = method; + this.alternatives = alternatives; + } + + public CrySLMethod getMethod() { + return method; + } + + public Collection getAlternatives() { + return alternatives; + } + + @Override + public String toString() { + final StringBuilder forbiddenMethod = new StringBuilder(); + forbiddenMethod.append(method.toString()); + if (!alternatives.isEmpty()) { + forbiddenMethod.append(" Alternatives: "); + } + + for (CrySLMethod meth : alternatives) { + forbiddenMethod.append(meth.toString()); + } + + return forbiddenMethod.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof CrySLForbiddenMethod)) { + return false; + } + + CrySLForbiddenMethod other = (CrySLForbiddenMethod) obj; + if (!method.equals(other.getMethod())) { + return false; + } + + return alternatives.equals(other.getAlternatives()); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLLiteral.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLLiteral.java new file mode 100644 index 0000000..4f328d8 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLLiteral.java @@ -0,0 +1,6 @@ +package de.darmstadt.tu.crossing.rule; + +public abstract class CrySLLiteral implements ISLConstraint { + + protected CrySLLiteral() {} +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLMethod.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLMethod.java new file mode 100644 index 0000000..7efc085 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLMethod.java @@ -0,0 +1,121 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.List; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +public class CrySLMethod implements ICrySLPredicateParameter { + + public static final String VOID = "void"; + public static final String ANY_TYPE = "AnyType"; + public static final String NO_NAME = "_"; + + private final String methodName; + private final String declaringClassName; + private final Entry retObject; + + /** + * List of Parameters, where a Parameter is an {@link Entry} of Name and Type, both as {@link + * String}. + */ + private final List> parameters; + + public CrySLMethod( + String methodName, + String declaringClassName, + List> parameters, + Entry retObject) { + this.methodName = methodName; + this.declaringClassName = declaringClassName; + this.parameters = parameters; + this.retObject = retObject; + } + + /** + * @return the FQ methodName + */ + public String getMethodName() { + return methodName; + } + + /** + * @return the short methodName + */ + public String getShortMethodName() { + return methodName.substring(methodName.lastIndexOf(".") + 1); + } + + public String getDeclaringClassName() { + return declaringClassName; + } + + /** + * @return the parameters + */ + public List> getParameters() { + return parameters; + } + + public Entry getRetObject() { + return retObject; + } + + public String toString() { + return getName(); + } + + public String getSignature() { + return getMethodName() + + "(" + + parameters.stream() + .map(param -> String.format("%s", param.getValue())) + .collect(Collectors.joining(", ")) + + ")"; + } + + @Override + public String getName() { + StringBuilder stmntBuilder = new StringBuilder(); + String returnValue = retObject.getKey(); + if (!"_".equals(returnValue)) { + stmntBuilder.append(returnValue); + stmntBuilder.append(" = "); + } + + stmntBuilder.append(this.methodName); + stmntBuilder.append("("); + + stmntBuilder.append( + parameters.stream() + .map(param -> String.format("%s %s", param.getValue(), param.getKey())) + .collect(Collectors.joining(", "))); + + stmntBuilder.append(");"); + return stmntBuilder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((methodName == null) ? 0 : methodName.hashCode()); + result = prime * result + ((parameters == null) ? 0 : parameters.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof CrySLMethod)) { + return false; + } + CrySLMethod other = (CrySLMethod) obj; + return this.getMethodName().equals(other.getMethodName()) + && parameters.equals(other.parameters); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLObject.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLObject.java new file mode 100644 index 0000000..dff929d --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLObject.java @@ -0,0 +1,64 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Arrays; + +public class CrySLObject implements ICrySLPredicateParameter { + + private final String varName; + private final String javaType; + private final CrySLSplitter splitter; + + public CrySLObject(String name, String type) { + this(name, type, null); + } + + public CrySLObject(String name, String type, CrySLSplitter part) { + varName = name; + javaType = type; + splitter = part; + } + + /** + * @return the varName + */ + public String getVarName() { + return varName; + } + + /** + * @return the splitter + */ + public CrySLSplitter getSplitter() { + return splitter; + } + + @Override + public boolean equals(Object other) { + if (other == this) return true; + if (other == null) return false; + if (!(other instanceof CrySLObject)) return false; + CrySLObject object = (CrySLObject) other; + return this.getJavaType().equals(object.getJavaType()) + && this.getName().equals(object.getName()) + && (this.getSplitter() == null || this.getSplitter().equals(object.getSplitter())); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[] {varName, javaType, splitter}); + } + + @Override + public String toString() { + return javaType + " " + varName + ((splitter != null) ? splitter.toString() : ""); + } + + @Override + public String getName() { + return varName; + } + + public String getJavaType() { + return javaType; + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLPredicate.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLPredicate.java new file mode 100644 index 0000000..f0e7fd7 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLPredicate.java @@ -0,0 +1,156 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class CrySLPredicate extends CrySLLiteral { + + protected final ICrySLPredicateParameter baseObject; + protected final String predName; + protected final List parameters; + protected final boolean negated; + protected final ISLConstraint constraint; + + public CrySLPredicate( + ICrySLPredicateParameter baseObject, + String name, + List parameters, + Boolean negated) { + this(baseObject, name, parameters, negated, null); + } + + public CrySLPredicate( + ICrySLPredicateParameter baseObject, + String name, + List parameters, + Boolean negated, + ISLConstraint constraint) { + this.baseObject = baseObject; + this.predName = name; + this.parameters = parameters; + this.negated = negated; + this.constraint = constraint; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[] {predName, parameters.size(), negated}); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof CrySLPredicate)) { + return false; + } + + CrySLPredicate other = (CrySLPredicate) obj; + if (baseObject == null) { + if (other.getBaseObject() != null) return false; + } else if (!baseObject.equals(other.getBaseObject())) { + return false; + } + + if (predName == null) { + if (other.getPredName() != null) return false; + } else if (!predName.equals(other.getPredName())) { + return false; + } + + if (parameters == null) { + if (other.getParameters() != null) return false; + } else if (parameters.size() != other.getParameters().size()) { + return false; + } + + return negated == other.isNegated(); + } + + /** + * @return the baseObject + */ + public ICrySLPredicateParameter getBaseObject() { + return baseObject; + } + + /** + * @return the predName + */ + public String getPredName() { + return predName; + } + + /** + * @return the optConstraint + */ + public Optional getConstraint() { + return Optional.ofNullable(constraint); + } + + /** + * @return the parameters + */ + public List getParameters() { + return parameters; + } + + /** + * @return the negated + */ + public Boolean isNegated() { + return negated; + } + + @Override + public String toString() { + StringBuilder predSB = new StringBuilder(); + if (negated) predSB.append("!"); + predSB.append(predName); + predSB.append("("); + predSB.append(parameters.stream().map(Object::toString).collect(Collectors.joining(", "))); + predSB.append(")"); + + return predSB.toString(); + } + + @Override + public List getInvolvedVarNames() { + List varNames = new ArrayList<>(); + if (Arrays.asList(new String[] {"neverTypeOf", "instanceOf"}).contains(predName)) { + varNames.add(parameters.get(0).getName()); + } else { + for (ICrySLPredicateParameter var : parameters) { + if (!("_".equals(var.getName()) + || "this".equals(var.getName()) + || var instanceof CrySLMethod)) { + varNames.add(var.getName()); + } + } + } + if (getBaseObject() != null) varNames.add(getBaseObject().getName()); + return varNames; + } + + public CrySLPredicate invertNegation() { + return new CrySLPredicate(baseObject, predName, parameters, !negated); + } + + public CrySLPredicate toNormalCrySLPredicate() { + return new CrySLPredicate(baseObject, predName, parameters, negated, constraint); + } + + @Override + public String getName() { + if (parameters.size() == 1) { + return parameters.get(0).getName(); + } else { + return ""; + } + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLRule.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLRule.java new file mode 100644 index 0000000..9c43463 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLRule.java @@ -0,0 +1,171 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; + +public class CrySLRule { + + private final String className; + + private final Collection> objects; + + private final Collection forbiddenMethods; + + private final Collection events; + + private final StateMachineGraph usagePattern; + + private final Collection constraints; + + private final Collection predicates; + + private final Collection negatedPredicates; + + public CrySLRule( + String className, + Collection> objects, + Collection forbiddenMethods, + Collection events, + StateMachineGraph usagePattern, + Collection constraints, + Collection predicates, + Collection negatedPredicates) { + this.className = className; + this.objects = objects; + this.forbiddenMethods = forbiddenMethods; + this.events = events; + this.usagePattern = usagePattern; + this.constraints = constraints; + this.predicates = predicates; + this.negatedPredicates = negatedPredicates; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CrySLRule) { + return ((CrySLRule) obj).getClassName().equals(className); + } + return false; + } + + @Override + public int hashCode() { + return 31 * className.hashCode(); + } + + /** + * @return the className + */ + public String getClassName() { + return className; + } + + /** + * @return the objects + */ + public Collection> getObjects() { + return objects; + } + + /** + * @return the forbiddenMethods + */ + public Collection getForbiddenMethods() { + return forbiddenMethods; + } + + /** + * @return the events + */ + public Collection getEvents() { + return events; + } + + /** + * @return the usagePattern + */ + public StateMachineGraph getUsagePattern() { + return usagePattern; + } + + /** + * @return the constraints + */ + public Collection getConstraints() { + return constraints; + } + + /** + * @return the predicates + */ + public Collection getPredicates() { + return predicates; + } + + /** + * @return the negated predicates + */ + public Collection getNegatedPredicates() { + return negatedPredicates; + } + + /** + * @return the constraints + */ + public Collection getRequiredPredicates() { + Collection requires = new LinkedList<>(); + for (ISLConstraint con : constraints) { + if (con instanceof CrySLPredicate) { + requires.add((CrySLPredicate) con); + } + } + return requires; + } + + @Override + public String toString() { + StringBuilder outputSB = new StringBuilder(); + + outputSB.append(this.className); + + outputSB.append("\nforbiddenMethods:"); + for (CrySLForbiddenMethod forMethSig : this.forbiddenMethods) { + outputSB.append(forMethSig); + outputSB.append(", "); + } + + outputSB.append("\nEvents:"); + for (CrySLMethod method : events) { + outputSB.append(method); + outputSB.append(", "); + } + + outputSB.append("\nUsage Pattern:"); + outputSB.append(this.usagePattern); + + outputSB.append("\nConstraints:"); + for (ISLConstraint constraint : this.constraints) { + outputSB.append(constraint); + outputSB.append(", "); + } + + if (this.predicates != null) { + outputSB.append("\nPredicates:"); + for (CrySLPredicate predicate : this.predicates) { + outputSB.append(predicate); + outputSB.append(", "); + } + } + + if (this.negatedPredicates != null) { + outputSB.append("\nNegated predicates:"); + for (CrySLPredicate predicate : this.negatedPredicates) { + outputSB.append(predicate); + outputSB.append(", "); + } + } + + return outputSB.toString(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLSplitter.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLSplitter.java new file mode 100644 index 0000000..f32a97c --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLSplitter.java @@ -0,0 +1,32 @@ +package de.darmstadt.tu.crossing.rule; + +public class CrySLSplitter { + + private final int index; + private final String split; + + public CrySLSplitter(int ind, String spl) { + this.index = ind; + this.split = spl; + } + + public int getIndex() { + return index; + } + + public String getSplitter() { + return split; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof CrySLSplitter)) return false; + + CrySLSplitter splitter = (CrySLSplitter) other; + return this.index == splitter.getIndex() && this.split.equals(splitter.getSplitter()); + } + + public String toString() { + return ".split(" + split + ")[" + index + "]"; + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLValueConstraint.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLValueConstraint.java new file mode 100644 index 0000000..989f69e --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/CrySLValueConstraint.java @@ -0,0 +1,60 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.ArrayList; +import java.util.List; + +public class CrySLValueConstraint extends CrySLLiteral { + + CrySLObject var; + List valueRange; + + public CrySLValueConstraint(CrySLObject name, List values) { + var = name; + valueRange = values; + } + + /** + * @return the varName + */ + public String getVarName() { + return var.getVarName(); + } + + /** + * @return the varName + */ + public CrySLObject getVar() { + return var; + } + + /** + * @return the valueRange + */ + public List getValueRange() { + return valueRange; + } + + public String toString() { + StringBuilder vCSB = new StringBuilder(); + vCSB.append("VC:"); + vCSB.append(var); + vCSB.append(" - "); + for (String value : valueRange) { + vCSB.append(value); + vCSB.append(","); + } + return vCSB.toString(); + } + + @Override + public List getInvolvedVarNames() { + List varNames = new ArrayList<>(); + varNames.add(var.getVarName()); + return varNames; + } + + @Override + public String getName() { + return toString(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/FiniteStateMachine.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/FiniteStateMachine.java new file mode 100644 index 0000000..2f16e4b --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/FiniteStateMachine.java @@ -0,0 +1,11 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Collection; + +public interface FiniteStateMachine { + Transition getInitialTransition(); + + Collection getAcceptingStates(); + + Collection> getAllTransitions(); +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/ICrySLPredicateParameter.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/ICrySLPredicateParameter.java new file mode 100644 index 0000000..6eca9d5 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/ICrySLPredicateParameter.java @@ -0,0 +1,6 @@ +package de.darmstadt.tu.crossing.rule; + +public interface ICrySLPredicateParameter { + + String getName(); +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/ISLConstraint.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/ISLConstraint.java new file mode 100644 index 0000000..14d009b --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/ISLConstraint.java @@ -0,0 +1,8 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.List; + +public interface ISLConstraint extends ICrySLPredicateParameter { + + List getInvolvedVarNames(); +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateMachineGraph.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateMachineGraph.java new file mode 100644 index 0000000..83779cb --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateMachineGraph.java @@ -0,0 +1,189 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.stream.Collectors; + +public final class StateMachineGraph implements FiniteStateMachine { + + private StateNode startNode; + private final Collection nodes; + private final Collection edges; + private final Collection initialEdges; + private int nodeNameCounter = 0; + + public StateMachineGraph() { + nodes = new HashSet<>(); + edges = new ArrayList<>(); + initialEdges = new ArrayList<>(); + } + + public StateNode createNewNode() { + StateNode node = new StateNode(String.valueOf(this.nodeNameCounter++), false, false); + this.nodes.add(node); + return node; + } + + public boolean createNewEdge(Collection methods, StateNode left, StateNode right) { + return this.addEdge(new TransitionEdge(methods, left, right)); + } + + private Boolean addEdge(TransitionEdge edge) { + final StateNode right = edge.getRight(); + final StateNode left = edge.getLeft(); + if (!(nodes.parallelStream().anyMatch(e -> e.equals(left)) + || nodes.parallelStream().anyMatch(e -> e.equals(right)))) { + return false; + } + if (edges.contains(edge)) { + return false; + } + edges.add(edge); + + if (left.isInitialState()) { + initialEdges.add(edge); + } + + return true; + } + + public void wrapUpCreation() { + getAcceptingStates().parallelStream() + .forEach( + e -> { + e.setHopsToAccepting(0); + updateHops(e); + }); + } + + public Collection getAllOutgoingEdges(StateNode node) { + return edges.parallelStream() + .filter(edge -> edge.from().equals(node)) + .collect(Collectors.toSet()); + } + + public void addAllOutgoingEdgesFromOneNodeToOtherNodes( + StateNode node, Collection otherNodes) { + Collection edgesFromNode = + edges.parallelStream() + .filter(e -> node.equals(e.from())) + .collect(Collectors.toList()); + otherNodes.forEach( + otherNode -> + edgesFromNode.forEach( + edge -> + this.createNewEdge( + edge.getLabel(), otherNode, edge.getLeft()))); + } + + public StateNode aggregateNodesToOneNode(Collection endNodes, StateNode newNode) { + this.aggregateNodesToOtherNodes(endNodes, Collections.singleton(newNode)); + return newNode; + } + + public Collection aggregateNodesToOtherNodes( + Collection nodesToAggr, Collection startNodes) { + Collection edgesToAnyAggrNode = + edges.parallelStream() + .filter(e -> nodesToAggr.contains(e.to())) + .collect(Collectors.toList()); + // Add new edges to newNode instead of Aggr Node + startNodes.forEach( + node -> + edgesToAnyAggrNode.forEach( + edgeToAggrNode -> + this.createNewEdge( + edgeToAggrNode.getLabel(), + edgeToAggrNode.getLeft(), + node))); + nodesToAggr.removeAll(startNodes); + removeNodesWithAllEdges(nodesToAggr); + return startNodes; + } + + private void removeNodesWithAllEdges(Collection nodesToRemove) { + nodesToRemove.forEach(this::removeNodeWithAllEdges); + } + + private void removeNodeWithAllEdges(StateNode node) { + removeAllEdgesHavingNode(node); + nodes.remove(node); + } + + private void removeAllEdgesHavingNode(StateNode node) { + Collection filteredEdges = + edges.parallelStream() + .filter(e -> node.equals(e.to()) || node.equals(e.from())) + .collect(Collectors.toList()); + edges.removeAll(filteredEdges); + } + + private void updateHops(StateNode node) { + int newPath = node.getHopsToAccepting() + 1; + getAllTransitions().parallelStream() + .forEach( + e -> { + StateNode theNewRight = e.getLeft(); + if (e.getRight().equals(node) + && theNewRight.getHopsToAccepting() > newPath) { + theNewRight.setHopsToAccepting(newPath); + updateHops(theNewRight); + } + }); + } + + public Boolean addNode(StateNode node) { + if (node.isInitialState()) { + this.startNode = node; + } + return nodes.parallelStream().anyMatch(n -> n.getName().equals(node.getName())) + ? false + : nodes.add(node); + } + + public String toString() { + StringBuilder graphSB = new StringBuilder(); + for (StateNode node : nodes) { + graphSB.append(node.toString()); + graphSB.append(System.lineSeparator()); + } + + for (TransitionEdge te : edges) { + graphSB.append(te.toString()); + graphSB.append(System.lineSeparator()); + } + + return graphSB.toString(); + } + + public Collection getNodes() { + return nodes; + } + + public StateNode getStartNode() { + return startNode; + } + + public Collection getEdges() { + return edges; + } + + public TransitionEdge getInitialTransition() { + throw new UnsupportedOperationException( + "There is no single initial transition. Use getInitialTransitions()"); + } + + public Collection getInitialTransitions() { + return initialEdges; + } + + public Collection getAcceptingStates() { + return nodes.parallelStream().filter(StateNode::getAccepting).collect(Collectors.toList()); + } + + public Collection getAllTransitions() { + return getEdges(); + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateMachineGraphReader.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateMachineGraphReader.java new file mode 100644 index 0000000..13bfc1e --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateMachineGraphReader.java @@ -0,0 +1,23 @@ +package de.darmstadt.tu.crossing.rule; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; + +public class StateMachineGraphReader { + public static StateMachineGraph readFromFile(File file) { + StateMachineGraph smg = null; + try { + FileInputStream fileIn = new FileInputStream(file); + ObjectInputStream in = new ObjectInputStream(fileIn); + smg = (StateMachineGraph) in.readObject(); + System.err.println(smg); + in.close(); + fileIn.close(); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + return smg; + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateNode.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateNode.java new file mode 100644 index 0000000..c7245f6 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/StateNode.java @@ -0,0 +1,100 @@ +package de.darmstadt.tu.crossing.rule; + +public class StateNode { + + private final String name; + + private final Boolean init; + private Boolean accepting; + private int hopsToAccepting = Integer.MAX_VALUE; + + public StateNode(String name) { + this(name, false, false); + } + + public StateNode(String name, Boolean init) { + this(name, init, false); + } + + public StateNode(String name, Boolean init, Boolean accepting) { + this.name = name; + this.init = init; + this.accepting = accepting; + } + + public String getName() { + return name; + } + + public Boolean getInit() { + return init; + } + + public Boolean getAccepting() { + return accepting; + } + + public void makeAccepting() { + this.accepting = true; + } + + public void setAccepting(Boolean accepting) { + this.accepting = accepting; + } + + public String toString() { + StringBuilder nodeSB = new StringBuilder(); + nodeSB.append("Name: "); + nodeSB.append(name); + nodeSB.append(" ("); + if (!accepting) { + nodeSB.append(hopsToAccepting + "hops to "); + } + nodeSB.append("accepting)"); + return nodeSB.toString(); + } + + public boolean isErrorState() { + return !accepting; + } + + public boolean isInitialState() { + return init; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((accepting == null) ? 0 : accepting.hashCode()); + result = prime * result + ((init == null) ? 0 : init.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + StateNode other = (StateNode) obj; + if (accepting == null) { + if (other.accepting != null) return false; + } else if (!accepting.equals(other.accepting)) return false; + if (init == null) { + if (other.init != null) return false; + } else if (!init.equals(other.init)) return false; + if (name == null) { + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; + return true; + } + + public void setHopsToAccepting(int hops) { + hopsToAccepting = hops; + } + + public int getHopsToAccepting() { + return hopsToAccepting; + } +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/Transition.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/Transition.java new file mode 100644 index 0000000..e95e3e8 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/Transition.java @@ -0,0 +1,11 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Collection; + +public interface Transition { + State from(); + + State to(); + + Collection getLabel(); +} diff --git a/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/TransitionEdge.java b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/TransitionEdge.java new file mode 100644 index 0000000..aad7275 --- /dev/null +++ b/CryslParser/src/main/java/de/darmstadt/tu/crossing/rule/TransitionEdge.java @@ -0,0 +1,74 @@ +package de.darmstadt.tu.crossing.rule; + +import java.util.Collection; + +public class TransitionEdge implements Transition { + + private final StateNode left; + private final StateNode right; + private final Collection methods; + + public TransitionEdge(Collection _methods, StateNode _left, StateNode _right) { + left = _left; + right = _right; + methods = _methods; + } + + public StateNode getLeft() { + return left; + } + + public StateNode getRight() { + return right; + } + + public Collection getLabel() { + return methods; + } + + @Override + public String toString() { + return "Left: " + + this.left.getName() + + " ====" + + methods + + "====> Right:" + + this.right.getName(); + } + + public StateNode from() { + return left; + } + + public StateNode to() { + return right; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((methods == null) ? 0 : methods.hashCode()); + result = prime * result + ((left == null) ? 0 : left.hashCode()); + result = prime * result + ((right == null) ? 0 : right.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + TransitionEdge other = (TransitionEdge) obj; + if (methods == null) { + if (other.methods != null) return false; + } else if (!methods.equals(other.methods)) return false; + if (left == null) { + if (other.left != null) return false; + } else if (!left.equals(other.left)) return false; + if (right == null) { + if (other.right != null) return false; + } else if (!right.equals(other.right)) return false; + return true; + } +} diff --git a/CryslParser/src/main/resources/plugin.properties b/CryslParser/src/main/resources/plugin.properties new file mode 100644 index 0000000..5377da1 --- /dev/null +++ b/CryslParser/src/main/resources/plugin.properties @@ -0,0 +1,2 @@ +# Required by emf.core +_UI_DiagnosticRoot_diagnostic=_UI_DiagnosticRoot_diagnostic diff --git a/CryslParser/src/test/java/parser/CrySLParserTest.java b/CryslParser/src/test/java/parser/CrySLParserTest.java new file mode 100644 index 0000000..28a231f --- /dev/null +++ b/CryslParser/src/test/java/parser/CrySLParserTest.java @@ -0,0 +1,65 @@ +package parser; + +import de.darmstadt.tu.crossing.CrySLParser; +import de.darmstadt.tu.crossing.rule.CrySLRule; +import java.io.IOException; +import java.util.Collection; +import org.junit.Assert; +import org.junit.Test; + +public class CrySLParserTest { + + private static final String emptyZipFilePath = "src/test/resources/parser/empty.zip"; + private static final String jcaRulesetZipFilePath = + "src/test/resources/parser/JavaCryptographicArchitecture-3.0.1-ruleset.zip"; + private static final String junkRuleSet = "src/test/resources/parser/rulesetWithJunk.zip"; + + @Test + public void testNumberOfRules() throws IOException { + CrySLParser parser = new CrySLParser(); + Collection rules = parser.parseRulesFromPath(jcaRulesetZipFilePath); + + Assert.assertEquals(49, rules.size()); + } + + @Test + public void testRulesZipFile() throws IOException { + CrySLParser parser = new CrySLParser(); + Collection rules = parser.parseRulesFromZipArchive(jcaRulesetZipFilePath); + + Assert.assertEquals(49, rules.size()); + } + + @Test + public void testJunkThrows() throws IOException { + CrySLParser parser = new CrySLParser(); + Collection rules = parser.parseRulesFromPath(junkRuleSet); + + Assert.assertEquals(48, rules.size()); + } + + @Test + public void testFileNoCrySLFiles() throws IOException { + CrySLParser parser = new CrySLParser(); + Collection rules = parser.parseRulesFromPath(emptyZipFilePath); + + Assert.assertEquals(0, rules.size()); + } + + @Test(expected = IOException.class) + public void testFileNotExists() throws IOException { + CrySLParser parser = new CrySLParser(); + Collection rules = parser.parseRulesFromPath("notExist"); + Assert.assertEquals(0, rules.size()); + } + + @Test + public void testRunTwiceSameResult() throws IOException { + CrySLParser parser = new CrySLParser(); + Collection rules = parser.parseRulesFromPath(jcaRulesetZipFilePath); + Assert.assertEquals(49, rules.size()); + + rules = parser.parseRulesFromPath(jcaRulesetZipFilePath); + Assert.assertEquals(49, rules.size()); + } +} diff --git a/CryslParser/src/test/java/statemachine/StateMachineTest.java b/CryslParser/src/test/java/statemachine/StateMachineTest.java new file mode 100644 index 0000000..8a4960d --- /dev/null +++ b/CryslParser/src/test/java/statemachine/StateMachineTest.java @@ -0,0 +1,76 @@ +package statemachine; + +import de.darmstadt.tu.crossing.CrySLParser; +import de.darmstadt.tu.crossing.parsing.CrySLException; +import de.darmstadt.tu.crossing.rule.CrySLRule; +import de.darmstadt.tu.crossing.rule.StateMachineGraph; +import de.darmstadt.tu.crossing.rule.TransitionEdge; +import java.io.File; +import java.util.Collection; +import org.junit.Assert; +import org.junit.Test; + +public class StateMachineTest { + + private static final String TEST_PATH = + "." + + File.separator + + "src" + + File.separator + + "test" + + File.separator + + "resources" + + File.separator + + "stateMachineRules" + + File.separator; + private static final String CRYSL_FILE = "StateMachineTester.crysl"; + + @Test + public void testOptionalAfterStar() { + // (Op1?, Op2, Op3)* + StateMachineGraph smg = getStateMachineGraph("optionalAfterStar"); + Collection edges = smg.getEdges(); + + Assert.assertEquals(edges.size(), 7); + Assert.assertTrue(stateMachineContainsEdge("0", "1", edges)); + Assert.assertTrue(stateMachineContainsEdge("0", "2", edges)); + Assert.assertTrue(stateMachineContainsEdge("3", "1", edges)); + Assert.assertTrue(stateMachineContainsEdge("3", "2", edges)); + } + + @Test + public void testOptionalBetweenStar() { + // (Op1, Op2?, Op3)* + StateMachineGraph smg = getStateMachineGraph("optionalBetweenStar"); + Collection edges = smg.getEdges(); + + Assert.assertEquals(edges.size(), 6); + Assert.assertTrue(stateMachineContainsEdge("1", "2", edges)); + Assert.assertTrue(stateMachineContainsEdge("1", "3", edges)); + Assert.assertTrue(stateMachineContainsEdge("3", "1", edges)); + Assert.assertFalse(stateMachineContainsEdge("3", "2", edges)); + } + + private StateMachineGraph getStateMachineGraph(String ruleName) { + try { + CrySLParser parser = new CrySLParser(); + + CrySLRule rule = + parser.parseRuleFromFile( + new File(TEST_PATH + ruleName + File.separator + CRYSL_FILE)); + return rule.getUsagePattern(); + } catch (CrySLException e) { + throw new RuntimeException("Unable to find rules for " + ruleName, e); + } + } + + private boolean stateMachineContainsEdge( + String from, String to, Collection edges) { + for (TransitionEdge edge : edges) { + if (edge.from().getName().equals(from) && edge.to().getName().equals(to)) { + return true; + } + } + return false; + } +} diff --git a/CryslParser/src/test/java/statemachine/StateMachineTester.java b/CryslParser/src/test/java/statemachine/StateMachineTester.java new file mode 100644 index 0000000..aeec9c0 --- /dev/null +++ b/CryslParser/src/test/java/statemachine/StateMachineTester.java @@ -0,0 +1,12 @@ +package statemachine; + +public class StateMachineTester { + + public void operation1() {} + + public void operation2() {} + + public void operation3() {} + + public void operation4() {} +} diff --git a/CryslParser/src/test/resources/parser/JavaCryptographicArchitecture-3.0.1-ruleset.zip b/CryslParser/src/test/resources/parser/JavaCryptographicArchitecture-3.0.1-ruleset.zip new file mode 100644 index 0000000..a9f6145 Binary files /dev/null and b/CryslParser/src/test/resources/parser/JavaCryptographicArchitecture-3.0.1-ruleset.zip differ diff --git a/CryslParser/src/test/resources/parser/Multiple-rulesets.zip b/CryslParser/src/test/resources/parser/Multiple-rulesets.zip new file mode 100644 index 0000000..1eb076c Binary files /dev/null and b/CryslParser/src/test/resources/parser/Multiple-rulesets.zip differ diff --git a/CryslParser/src/test/resources/parser/empty.zip b/CryslParser/src/test/resources/parser/empty.zip new file mode 100644 index 0000000..15cb0ec Binary files /dev/null and b/CryslParser/src/test/resources/parser/empty.zip differ diff --git a/CryslParser/src/test/resources/parser/rulesetWithJunk.zip b/CryslParser/src/test/resources/parser/rulesetWithJunk.zip new file mode 100644 index 0000000..8bfa125 Binary files /dev/null and b/CryslParser/src/test/resources/parser/rulesetWithJunk.zip differ diff --git a/CryslParser/src/test/resources/stateMachineRules/optionalAfterStar/StateMachineTester.crysl b/CryslParser/src/test/resources/stateMachineRules/optionalAfterStar/StateMachineTester.crysl new file mode 100644 index 0000000..e687027 --- /dev/null +++ b/CryslParser/src/test/resources/stateMachineRules/optionalAfterStar/StateMachineTester.crysl @@ -0,0 +1,11 @@ +SPEC statemachine.StateMachineTester + +EVENTS + Con: StateMachineTester(); + Op1: operation1(); + Op2: operation2(); + Op3: operation3(); + Op4: operation4(); + +ORDER + Con, (Op1?, Op2, Op3)* diff --git a/CryslParser/src/test/resources/stateMachineRules/optionalBetweenStar/StateMachineTester.crysl b/CryslParser/src/test/resources/stateMachineRules/optionalBetweenStar/StateMachineTester.crysl new file mode 100644 index 0000000..74a1483 --- /dev/null +++ b/CryslParser/src/test/resources/stateMachineRules/optionalBetweenStar/StateMachineTester.crysl @@ -0,0 +1,11 @@ +SPEC statemachine.StateMachineTester + +EVENTS + Con: StateMachineTester(); + Op1: operation1(); + Op2: operation2(); + Op3: operation3(); + Op4: operation4(); + +ORDER + Con, (Op1, Op2?, Op3)* diff --git a/de.darmstadt.tu.crossing.CrySL/pom.xml b/de.darmstadt.tu.crossing.CrySL/pom.xml index 80fae9b..cc41351 100644 --- a/de.darmstadt.tu.crossing.CrySL/pom.xml +++ b/de.darmstadt.tu.crossing.CrySL/pom.xml @@ -33,7 +33,6 @@ - diff --git a/pom.xml b/pom.xml index 1a76fa5..f94d347 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,7 @@ + CrySLParser de.darmstadt.tu.crossing.CrySL de.darmstadt.tu.crossing.CrySL.ide de.darmstadt.tu.crossing.CrySL.ui @@ -55,12 +56,10 @@ 2.35.0 2.19.0 UTF-8 - 1.8 - 1.8 + 11 -