AttributesRule.java

  1. /*
  2.  * Copyright (C) 2010, 2017 Red Hat Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.attributes;

  11. import static org.eclipse.jgit.ignore.IMatcher.NO_MATCH;

  12. import java.util.ArrayList;
  13. import java.util.Collections;
  14. import java.util.List;

  15. import org.eclipse.jgit.attributes.Attribute.State;
  16. import org.eclipse.jgit.errors.InvalidPatternException;
  17. import org.eclipse.jgit.ignore.FastIgnoreRule;
  18. import org.eclipse.jgit.ignore.IMatcher;
  19. import org.eclipse.jgit.ignore.internal.PathMatcher;

  20. /**
  21.  * A single attributes rule corresponding to one line in a .gitattributes file.
  22.  *
  23.  * Inspiration from: {@link org.eclipse.jgit.ignore.FastIgnoreRule}
  24.  *
  25.  * @since 3.7
  26.  */
  27. public class AttributesRule {

  28.     /**
  29.      * regular expression for splitting attributes - space, tab and \r (the C
  30.      * implementation oddly enough allows \r between attributes)
  31.      * */
  32.     private static final String ATTRIBUTES_SPLIT_REGEX = "[ \t\r]"; //$NON-NLS-1$

  33.     private static List<Attribute> parseAttributes(String attributesLine) {
  34.         // the C implementation oddly enough allows \r between attributes too.
  35.         ArrayList<Attribute> result = new ArrayList<>();
  36.         for (String attribute : attributesLine.split(ATTRIBUTES_SPLIT_REGEX)) {
  37.             attribute = attribute.trim();
  38.             if (attribute.length() == 0)
  39.                 continue;

  40.             if (attribute.startsWith("-")) {//$NON-NLS-1$
  41.                 if (attribute.length() > 1)
  42.                     result.add(new Attribute(attribute.substring(1),
  43.                             State.UNSET));
  44.                 continue;
  45.             }

  46.             if (attribute.startsWith("!")) {//$NON-NLS-1$
  47.                 if (attribute.length() > 1)
  48.                     result.add(new Attribute(attribute.substring(1),
  49.                             State.UNSPECIFIED));
  50.                 continue;
  51.             }

  52.             final int equalsIndex = attribute.indexOf('=');
  53.             if (equalsIndex == -1)
  54.                 result.add(new Attribute(attribute, State.SET));
  55.             else {
  56.                 String attributeKey = attribute.substring(0, equalsIndex);
  57.                 if (attributeKey.length() > 0) {
  58.                     String attributeValue = attribute
  59.                             .substring(equalsIndex + 1);
  60.                     result.add(new Attribute(attributeKey, attributeValue));
  61.                 }
  62.             }
  63.         }
  64.         return result;
  65.     }

  66.     private final String pattern;
  67.     private final List<Attribute> attributes;

  68.     private final boolean nameOnly;

  69.     private final boolean dirOnly;

  70.     private final IMatcher matcher;

  71.     /**
  72.      * Create a new attribute rule with the given pattern. Assumes that the
  73.      * pattern is already trimmed.
  74.      *
  75.      * @param pattern
  76.      *            Base pattern for the attributes rule. This pattern will be
  77.      *            parsed to generate rule parameters. It can not be
  78.      *            <code>null</code>.
  79.      * @param attributes
  80.      *            the rule attributes. This string will be parsed to read the
  81.      *            attributes.
  82.      */
  83.     public AttributesRule(String pattern, String attributes) {
  84.         this.attributes = parseAttributes(attributes);

  85.         if (pattern.endsWith("/")) { //$NON-NLS-1$
  86.             pattern = pattern.substring(0, pattern.length() - 1);
  87.             dirOnly = true;
  88.         } else {
  89.             dirOnly = false;
  90.         }

  91.         int slashIndex = pattern.indexOf('/');

  92.         if (slashIndex < 0) {
  93.             nameOnly = true;
  94.         } else if (slashIndex == 0) {
  95.             nameOnly = false;
  96.         } else {
  97.             nameOnly = false;
  98.             // Contains "/" but does not start with one
  99.             // Adding / to the start should not interfere with matching
  100.             pattern = "/" + pattern; //$NON-NLS-1$
  101.         }

  102.         IMatcher candidateMatcher = NO_MATCH;
  103.         try {
  104.             candidateMatcher = PathMatcher.createPathMatcher(pattern,
  105.                     Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly);
  106.         } catch (InvalidPatternException e) {
  107.             // ignore: invalid patterns are silently ignored
  108.         }
  109.         this.matcher = candidateMatcher;
  110.         this.pattern = pattern;
  111.     }

  112.     /**
  113.      * Whether to match directories only
  114.      *
  115.      * @return {@code true} if the pattern should match directories only
  116.      * @since 4.3
  117.      */
  118.     public boolean isDirOnly() {
  119.         return dirOnly;
  120.     }

  121.     /**
  122.      * Return the attributes.
  123.      *
  124.      * @return an unmodifiable list of attributes (never returns
  125.      *         <code>null</code>)
  126.      */
  127.     public List<Attribute> getAttributes() {
  128.         return Collections.unmodifiableList(attributes);
  129.     }

  130.     /**
  131.      * Whether the pattern is only a file name and not a path
  132.      *
  133.      * @return <code>true</code> if the pattern is just a file name and not a
  134.      *         path
  135.      */
  136.     public boolean isNameOnly() {
  137.         return nameOnly;
  138.     }

  139.     /**
  140.      * Get the pattern
  141.      *
  142.      * @return The blob pattern to be used as a matcher (never returns
  143.      *         <code>null</code>)
  144.      */
  145.     public String getPattern() {
  146.         return pattern;
  147.     }

  148.     /**
  149.      * Returns <code>true</code> if a match was made.
  150.      *
  151.      * @param relativeTarget
  152.      *            Name pattern of the file, relative to the base directory of
  153.      *            this rule
  154.      * @param isDirectory
  155.      *            Whether the target file is a directory or not
  156.      * @return True if a match was made.
  157.      */
  158.     public boolean isMatch(String relativeTarget, boolean isDirectory) {
  159.         if (relativeTarget == null)
  160.             return false;
  161.         if (relativeTarget.length() == 0)
  162.             return false;
  163.         boolean match = matcher.matches(relativeTarget, isDirectory, true);
  164.         return match;
  165.     }

  166.     /** {@inheritDoc} */
  167.     @Override
  168.     public String toString() {
  169.         StringBuilder sb = new StringBuilder();
  170.         sb.append(pattern);
  171.         for (Attribute a : attributes) {
  172.             sb.append(" "); //$NON-NLS-1$
  173.             sb.append(a);
  174.         }
  175.         return sb.toString();

  176.     }
  177. }