All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.oliviercailloux.umlgraders.AdminManagesUsers Maven / Gradle / Ivy

The newest version!
package io.github.oliviercailloux.umlgraders;

import static com.google.common.base.Verify.verify;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.graph.Graph;
import io.github.oliviercailloux.grade.Criterion;
import io.github.oliviercailloux.grade.CriterionGradeWeight;
import io.github.oliviercailloux.grade.DeadlineGrader;
import io.github.oliviercailloux.grade.GitGeneralGrader;
import io.github.oliviercailloux.grade.IGrade;
import io.github.oliviercailloux.grade.RepositoryFetcher;
import io.github.oliviercailloux.grade.WeightingGrade;
import io.github.oliviercailloux.grade.markers.Marks;
import io.github.oliviercailloux.grade.old.Mark;
import io.github.oliviercailloux.jaris.xml.DomHelper;
import io.github.oliviercailloux.utils.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class AdminManagesUsers {
  private static final String XMI_NS = "http://www.omg.org/spec/XMI/20131001";

  private static final String UML_NS = "http://www.eclipse.org/uml2/5.0.0/UML";

  @SuppressWarnings("unused")
  private static final Logger LOGGER = LoggerFactory.getLogger(AdminManagesUsers.class);

  public static final String PREFIX = "admin-manages-users";

  public static final ZonedDateTime DEADLINE =
      ZonedDateTime.parse("2021-01-25T14:11:00+01:00[Europe/Paris]");

  private static DocumentBuilder builder;

  private static final DomHelper domHelper = DomHelper.domHelper();

  public static void main(String[] args) throws Exception {
    final RepositoryFetcher fetcher = RepositoryFetcher.withPrefix(PREFIX);
    // .setRepositoriesFilter(r->r.getUsername().equals(""));
    final GitGeneralGrader grader = GitGeneralGrader.using(fetcher,
        DeadlineGrader.usingPathGrader(AdminManagesUsers::grade, DEADLINE).setPenalizer(
            DeadlineGrader.LinearPenalizer.proportionalToLateness(Duration.ofSeconds(600))));
    grader.grade();
  }

  private AdminManagesUsers() {}

  public static IGrade grade(Path work) throws IOException {
    final ImmutableSet.Builder gradeBuilder = ImmutableSet.builder();

    final ImmutableSet umlPaths =
        Utils.getPathsMatching(work, (Path p) -> String.valueOf(p.getFileName()).endsWith(".uml"));
    if (umlPaths.isEmpty()) {
      return Mark.zero("No model file found.");
    }

    for (Path umlPath : umlPaths) {
      Document uml;
      IGrade grade;
      try {
        uml = toDocument(umlPath);
        grade = grade(work, uml);
      } catch (SAXException e) {
        grade = Mark.zero("Could not parse " + umlPath.toString() + ": " + e.getMessage());
      }
      gradeBuilder.add(CriterionGradeWeight
          .from(Criterion.given("Considering " + umlPath.toString()), grade, 1d));
    }

    return WeightingGrade.from(gradeBuilder.build());
  }

  private static IGrade grade(Path work, Document uml) throws IOException {
    final ImmutableSet.Builder gradeBuilder = ImmutableSet.builder();
    gradeBuilder.add(
        CriterionGradeWeight.from(Criterion.given("Required elements"), getRequired(uml), 15d));
    gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("No superfluous elements"),
        gradeSuperfluous(uml), 1.5d));
    gradeBuilder
        .add(CriterionGradeWeight.from(Criterion.given("Sorted out"), gradeSortedOut(uml), 1d));
    gradeBuilder
        .add(CriterionGradeWeight.from(Criterion.given("Model only"), gradeModelOnly(work), 1.5d));
    return WeightingGrade.from(gradeBuilder.build());
  }

  private static IGrade gradeModelOnly(Path work) throws IOException {
    final ImmutableSet umlFiles =
        Utils.getPathsMatching(work, p -> String.valueOf(p.getFileName()).endsWith(".uml"));
    final ImmutableSet filesNotUml = Utils.getPathsMatching(work,
        p -> Files.isRegularFile(p) && !p.getFileName().toString().endsWith(".uml"));
    return Mark.binary(umlFiles.size() == 1 && filesNotUml.isEmpty());
  }

  private static IGrade gradeSortedOut(Document uml) {
    final ImmutableList children = DomHelper.toList(uml.getDocumentElement().getChildNodes());
    final ImmutableList nonTextChildren = children.stream()
        .filter(n -> n.getNodeType() != Node.TEXT_NODE).collect(ImmutableList.toImmutableList());
    final boolean justOneSubject = nonTextChildren.size() == 1;

    final ImmutableList pckgEls =
        DomHelper.toElements(uml.getElementsByTagName("packagedElement"));
    final ImmutableList ownedEls =
        DomHelper.toElements(uml.getElementsByTagName("ownedUseCase"));
    final ImmutableList nestedEls =
        DomHelper.toElements(uml.getElementsByTagName("nestedClassifier"));
    final ImmutableList mainElements =
        Streams.concat(pckgEls.stream(), ownedEls.stream(), nestedEls.stream())
            .collect(ImmutableList.toImmutableList());
    final ImmutableList useCases = getType(mainElements, "uml:UseCase");
    final boolean someUseCases = useCases.size() >= 1;
    return Mark.binary(justOneSubject && someUseCases);
  }

  private static IGrade gradeSuperfluous(Document uml) {
    int expectedFoundNotRemoved = 0;
    final ImmutableSet leaves = getLeaves(uml);
    final Set remainingLeaves = new LinkedHashSet<>(leaves);
    {
      final ImmutableList elements = getType(remainingLeaves, "uml:Actor");
      final long nbFound = elements.size();
      if (nbFound >= 1) {
        remainingLeaves.removeAll(elements);
      } else {
        expectedFoundNotRemoved += nbFound;
      }
    }
    {
      final ImmutableList elements = getType(remainingLeaves, "uml:UseCase");
      final long nbFound = elements.size();
      LOGGER.debug("Found UseCases: {}.", toString(elements));
      if (nbFound >= 3) {
        remainingLeaves.removeAll(elements);
      } else {
        expectedFoundNotRemoved += nbFound;
      }
    }
    {
      final ImmutableList elements = getType(remainingLeaves, "uml:Generalization");
      final long nbFound = elements.size();
      if (nbFound >= 2) {
        remainingLeaves.removeAll(elements);
      } else {
        expectedFoundNotRemoved += nbFound;
      }
    }
    {
      final ImmutableList elements =
          getType(remainingLeaves, "ecore:EStringToStringMapEntry");
      remainingLeaves.removeAll(elements);
    }
    {
      final ImmutableList elements = getType(remainingLeaves, "uml:Property");
      final long nbFound = elements.size();
      if (nbFound >= 2) {
        remainingLeaves.removeAll(elements);
      } else {
        expectedFoundNotRemoved += nbFound;
      }
    }
    final int nbSuperfluous = remainingLeaves.size() - expectedFoundNotRemoved;
    verify(nbSuperfluous >= 0);

    final int quartersLost = Math.min(nbSuperfluous, 4);
    final IGrade superfluous;
    if (leaves.size() <= 4) {
      superfluous = Mark.zero("Not enough elements");
    } else if (quartersLost == 0) {
      superfluous = Mark.one();
    } else {
      superfluous = Mark.given((4 - quartersLost) / 4d,
          "Nb superfluous: " + nbSuperfluous + " among " + toNameAndId(remainingLeaves) + ".");
    }
    return superfluous;
  }

  private static ImmutableSet getLeaves(Document uml) {
    final Graph graph =
        Utils.asGraph(
            (Element e) -> DomHelper.toList(e.getChildNodes()).stream()
                .filter(n -> n.getNodeType() == Node.ELEMENT_NODE).map(n -> (Element) n)
                .collect(ImmutableList.toImmutableList()),
            ImmutableSet.of(uml.getDocumentElement()));
    return graph.nodes().stream().filter(e -> graph.successors(e).isEmpty())
        .collect(ImmutableSet.toImmutableSet());
  }

  private static IGrade getRequired(Document uml) {
    final ImmutableSet.Builder gradeBuilder = ImmutableSet.builder();

    gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Model"),
        Mark.binary(uml.getDocumentElement().getNamespaceURI().equals(UML_NS)), 1d));

    final ImmutableList pckgEls =
        DomHelper.toElements(uml.getElementsByTagName("packagedElement"));
    final ImmutableList ownedEls =
        DomHelper.toElements(uml.getElementsByTagName("ownedUseCase"));
    final ImmutableList nestedEls =
        DomHelper.toElements(uml.getElementsByTagName("nestedClassifier"));

    final Optional subject = pckgEls.stream()
        .filter(e -> Marks.extendAll("System").matcher(e.getAttribute("name")).matches())
        .collect(Utils.singleOrEmpty());
    gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Subject name"),
        Mark.binary(subject.isPresent()), 1.5d));

    final ImmutableList mainElements =
        Streams.concat(pckgEls.stream(), ownedEls.stream(), nestedEls.stream())
            .collect(ImmutableList.toImmutableList());
    final ImmutableList useCaseEls = getType(mainElements, "uml:UseCase");

    final ImmutableSet manageUseCases = getUseCases(mainElements, "Manage\\h+user?s");
    final ImmutableSet manageUseCaseIds =
        manageUseCases.stream().map(AdminManagesUsers::getId).flatMap(Optional::stream)
            .collect(ImmutableSet.toImmutableSet());
    LOGGER.debug("Manage: {}.", manageUseCaseIds);
    final Optional manageUseCase = manageUseCases.stream().collect(Utils.singleOrEmpty());
    gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Use case Manage"),
        gradeUseCases(manageUseCases), 2d));

    final ImmutableSet createUseCases = getUseCases(mainElements, "Create\\h+user");
    gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Use case Create"),
        gradeUseCases(createUseCases), 2d));

    final ImmutableSet deleteUseCases = getUseCases(mainElements, "Delete\\h+user");
    gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Use case Delete"),
        gradeUseCases(deleteUseCases), 2d));

    {
      final ImmutableSet childrenUseCases =
          Stream.concat(createUseCases.stream(), deleteUseCases.stream())
              .collect(ImmutableSet.toImmutableSet());

      final ImmutableSet useCasesWithGeneralization = useCaseEls.stream()
          .filter(u -> getTargetOfUniqueGeneralization(u, useCaseEls).isPresent())
          .collect(ImmutableSet.toImmutableSet());
      final boolean createGeneralized = !createUseCases.isEmpty() && createUseCases.stream()
          .allMatch(u -> getTargetOfUniqueGeneralization(u, useCaseEls).isPresent());
      final boolean deleteGeneralized = !deleteUseCases.isEmpty() && deleteUseCases.stream()
          .allMatch(u -> getTargetOfUniqueGeneralization(u, useCaseEls).isPresent());
      final CriterionGradeWeight nbOneOrTwo =
          CriterionGradeWeight.from(Criterion.given("Create or Delete generalizes"),
              Mark.binary(createGeneralized || deleteGeneralized), 2d);
      final CriterionGradeWeight nbTwo =
          CriterionGradeWeight.from(Criterion.given("Create and Delete generalize"),
              Mark.binary(createGeneralized && deleteGeneralized), 1d);
      final CriterionGradeWeight number = CriterionGradeWeight.from(Criterion.given("Number"),
          WeightingGrade.from(ImmutableSet.of(nbOneOrTwo, nbTwo)), 1d);

      final ImmutableSet targets = useCasesWithGeneralization.stream()
          .map(u -> getTargetOfUniqueGeneralization(u, useCaseEls)).map(Optional::get)
          .collect(ImmutableSet.toImmutableSet());
      final Optional targetIfSingle = targets.stream().collect(Utils.singleOrEmpty());
      final boolean allTargetsManage =
          targetIfSingle.isPresent() && targetIfSingle.equals(manageUseCase);
      final CriterionGradeWeight idsOneOrTwo =
          CriterionGradeWeight.from(Criterion.given("One or two"),
              Mark.binary(
                  allTargetsManage && childrenUseCases.containsAll(useCasesWithGeneralization)),
              2d);
      final CriterionGradeWeight idsTwo = CriterionGradeWeight.from(Criterion.given("Exactly two"),
          Mark.binary(allTargetsManage && childrenUseCases.equals(useCasesWithGeneralization)), 1d);
      final CriterionGradeWeight ids =
          CriterionGradeWeight.from(Criterion.given("Generalizes is the unique Manage UC"),
              WeightingGrade.from(ImmutableSet.of(idsOneOrTwo, idsTwo)), 1d);
      gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Generalization"),
          WeightingGrade.from(ImmutableSet.of(number, ids)), 3d));
    }
    final Optional actor = Stream.concat(pckgEls.stream(), nestedEls.stream())
        .filter(e -> e.getAttributeNS(XMI_NS, "type").equals("uml:Actor"))
        .collect(Utils.singleOrEmpty());
    final Optional actorId = actor.flatMap(AdminManagesUsers::getId);
    {
      gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Actor"), gradeActor(actor), 1d));
    }
    {
      final Optional association = Stream.concat(pckgEls.stream(), nestedEls.stream())
          .filter(e -> e.getAttributeNS(XMI_NS, "type").equals("uml:Association"))
          .collect(Utils.singleOrEmpty());
      gradeBuilder.add(CriterionGradeWeight.from(Criterion.given("Association"),
          gradeAssociation(association, actorId, manageUseCaseIds), 2.5d));

    }
    return WeightingGrade.from(gradeBuilder.build());
  }

  static ImmutableSet toString(Collection elements) {
    return elements.stream().map(domHelper::toString).collect(ImmutableSet.toImmutableSet());
  }

  static ImmutableSet toNameAndId(Set elements) {
    return elements.stream().map(e -> e.getAttribute("name") + " " + e.getAttributeNS(XMI_NS, "id"))
        .collect(ImmutableSet.toImmutableSet());
  }

  private static IGrade gradeActor(Optional actor) {
    final CriterionGradeWeight actorGrade =
        CriterionGradeWeight.from(Criterion.given("Actor"), Mark.binary(actor.isPresent()), 1d);
    final CriterionGradeWeight nameGrade =
        CriterionGradeWeight.from(Criterion.given("Name"), Mark.binary(Marks.extendAll("Admin")
            .matcher(actor.map(e -> e.getAttribute("name")).orElse("")).matches()), 1d);
    return WeightingGrade.from(ImmutableSet.of(actorGrade, nameGrade));
  }

  private static IGrade gradeAssociation(Optional association, Optional actorId,
      Set manageUseCaseIds) {
    final CriterionGradeWeight associationGrade = CriterionGradeWeight
        .from(Criterion.given("Actor"), Mark.binary(association.isPresent()), 1d);
    final ImmutableList ownedAttributes =
        association.map(a -> DomHelper.toElements(a.getElementsByTagName("ownedAttribute")))
            .orElse(ImmutableList.of());
    final ImmutableList ownedEnds =
        association.map(a -> DomHelper.toElements(a.getElementsByTagName("ownedEnd")))
            .orElse(ImmutableList.of());
    final ImmutableList owned =
        ImmutableList.builder().addAll(ownedAttributes).addAll(ownedEnds).build();
    final ImmutableList properties =
        owned.stream().filter(a -> a.getAttributeNS(XMI_NS, "type").equals("uml:Property"))
            .collect(ImmutableList.toImmutableList());
    final ImmutableList targetIds = properties.stream().map(p -> p.getAttribute("type"))
        .collect(ImmutableList.toImmutableList());
    final boolean targetsAnActor = actorId.isPresent() && targetIds.contains(actorId.get());
    final ImmutableSet targettedUcIds =
        Sets.intersection(ImmutableSet.copyOf(targetIds), manageUseCaseIds).immutableCopy();
    final boolean targetsAnUc = targettedUcIds.size() == 1;
    LOGGER.debug("Targets: {}, to ucs: {}.", targetIds, targettedUcIds);
    final CriterionGradeWeight propertiesGrade = CriterionGradeWeight
        .from(Criterion.given("Properties"), Mark.binary(targetsAnActor && targetsAnUc), 1d);

    return WeightingGrade.from(ImmutableSet.of(associationGrade, propertiesGrade));
  }

  private static Optional getTargetOfUniqueGeneralization(Element useCaseEl,
      List useCaseEls) {
    final Optional generalizationEl =
        DomHelper.toElements(useCaseEl.getElementsByTagName("generalization")).stream()
            .collect(Utils.singleOrEmpty());
    final Optional targetId = generalizationEl.map(g -> g.getAttribute("general"));
    final Optional useCase = targetId.flatMap(g -> getElementById(useCaseEls, g));
    return useCase;
  }

  private static Optional getElementById(List elements, String id) {
    return elements.stream().filter(e -> e.getAttributeNS(XMI_NS, "id").equals(id))
        .collect(Utils.singleOrEmpty());
  }

  private static WeightingGrade gradeUseCases(Set useCases) {
    final Set useCaseIds = useCases.stream().map(AdminManagesUsers::getId)
        .flatMap(Optional::stream).collect(ImmutableSet.toImmutableSet());
    final CriterionGradeWeight ucGrade = CriterionGradeWeight.from(Criterion.given("Exists"),
        Mark.binary(!useCases.isEmpty() && useCaseIds.size() == useCases.size()), 1d);
    final CriterionGradeWeight nameGrade =
        CriterionGradeWeight.from(Criterion.given("Subject"),
            Mark.binary(
                !useCases.isEmpty() && useCases.stream().allMatch(u -> u.hasAttribute("subject"))),
            2d);
    final CriterionGradeWeight subjectGrade =
        CriterionGradeWeight.from(Criterion.given("Unique"), Mark.binary(useCases.size() == 1), 3d);
    return WeightingGrade.from(ImmutableSet.of(ucGrade, nameGrade, subjectGrade));
  }

  private static Optional getId(Element element) {
    return Optional.ofNullable(Strings.emptyToNull(element.getAttributeNS(XMI_NS, "id")));
  }

  private static ImmutableSet getUseCases(Iterable elements, String namePattern) {
    final ImmutableList useCases = getType(elements, "uml:UseCase");
    return useCases.stream()
        .filter(e -> Marks.extendAll(namePattern).matcher(e.getAttribute("name")).matches())
        .collect(ImmutableSet.toImmutableSet());
  }

  private static ImmutableList getType(Iterable elements, String type) {
    return Streams.stream(elements).filter(e -> e.getAttributeNS(XMI_NS, "type").equals(type))
        .collect(ImmutableList.toImmutableList());
  }

  private static void prepareBuilder() {
    if (builder == null) {
      final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      try {
        builder = factory.newDocumentBuilder();
      } catch (ParserConfigurationException e) {
        throw new IllegalStateException(e);
      }
    }
  }

  public static Document toDocument(Path input) throws SAXException, IOException {
    prepareBuilder();

    final Document doc;
    try (InputStream inputStream = Files.newInputStream(input)) {
      final InputSource source = new InputSource(inputStream);
      source.setSystemId(input.toUri().toString());
      doc = builder.parse(source);
    }

    final Element docE = doc.getDocumentElement();
    LOGGER.debug("Main tag name: {}.", docE.getTagName());

    return doc;
  }

  public static Document asDocument(InputSource input) {
    prepareBuilder();

    final Document doc;
    try {
      doc = builder.parse(input);
    } catch (SAXException | IOException e) {
      throw new IllegalStateException(e);
    }

    final Element docE = doc.getDocumentElement();
    LOGGER.debug("Main tag name: {}.", docE.getTagName());

    return doc;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy