View Javadoc
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  
12  import static org.eclipse.jgit.ignore.IMatcher.NO_MATCH;
13  
14  import java.util.ArrayList;
15  import java.util.Collections;
16  import java.util.List;
17  
18  import org.eclipse.jgit.attributes.Attribute.State;
19  import org.eclipse.jgit.errors.InvalidPatternException;
20  import org.eclipse.jgit.ignore.FastIgnoreRule;
21  import org.eclipse.jgit.ignore.IMatcher;
22  import org.eclipse.jgit.ignore.internal.PathMatcher;
23  
24  /**
25   * A single attributes rule corresponding to one line in a .gitattributes file.
26   *
27   * Inspiration from: {@link org.eclipse.jgit.ignore.FastIgnoreRule}
28   *
29   * @since 3.7
30   */
31  public class AttributesRule {
32  
33  	/**
34  	 * regular expression for splitting attributes - space, tab and \r (the C
35  	 * implementation oddly enough allows \r between attributes)
36  	 * */
37  	private static final String ATTRIBUTES_SPLIT_REGEX = "[ \t\r]"; //$NON-NLS-1$
38  
39  	private static List<Attribute> parseAttributes(String attributesLine) {
40  		// the C implementation oddly enough allows \r between attributes too.
41  		ArrayList<Attribute> result = new ArrayList<>();
42  		for (String attribute : attributesLine.split(ATTRIBUTES_SPLIT_REGEX)) {
43  			attribute = attribute.trim();
44  			if (attribute.length() == 0)
45  				continue;
46  
47  			if (attribute.startsWith("-")) {//$NON-NLS-1$
48  				if (attribute.length() > 1)
49  					result.add(new Attribute(attribute.substring(1),
50  							State.UNSET));
51  				continue;
52  			}
53  
54  			if (attribute.startsWith("!")) {//$NON-NLS-1$
55  				if (attribute.length() > 1)
56  					result.add(new Attribute(attribute.substring(1),
57  							State.UNSPECIFIED));
58  				continue;
59  			}
60  
61  			final int equalsIndex = attribute.indexOf('=');
62  			if (equalsIndex == -1)
63  				result.add(new Attribute(attribute, State.SET));
64  			else {
65  				String attributeKey = attribute.substring(0, equalsIndex);
66  				if (attributeKey.length() > 0) {
67  					String attributeValue = attribute
68  							.substring(equalsIndex + 1);
69  					result.add(new Attribute(attributeKey, attributeValue));
70  				}
71  			}
72  		}
73  		return result;
74  	}
75  
76  	private final String pattern;
77  	private final List<Attribute> attributes;
78  
79  	private final boolean nameOnly;
80  
81  	private final boolean dirOnly;
82  
83  	private final IMatcher matcher;
84  
85  	/**
86  	 * Create a new attribute rule with the given pattern. Assumes that the
87  	 * pattern is already trimmed.
88  	 *
89  	 * @param pattern
90  	 *            Base pattern for the attributes rule. This pattern will be
91  	 *            parsed to generate rule parameters. It can not be
92  	 *            <code>null</code>.
93  	 * @param attributes
94  	 *            the rule attributes. This string will be parsed to read the
95  	 *            attributes.
96  	 */
97  	public AttributesRule(String pattern, String attributes) {
98  		this.attributes = parseAttributes(attributes);
99  
100 		if (pattern.endsWith("/")) { //$NON-NLS-1$
101 			pattern = pattern.substring(0, pattern.length() - 1);
102 			dirOnly = true;
103 		} else {
104 			dirOnly = false;
105 		}
106 
107 		int slashIndex = pattern.indexOf('/');
108 
109 		if (slashIndex < 0) {
110 			nameOnly = true;
111 		} else if (slashIndex == 0) {
112 			nameOnly = false;
113 		} else {
114 			nameOnly = false;
115 			// Contains "/" but does not start with one
116 			// Adding / to the start should not interfere with matching
117 			pattern = "/" + pattern; //$NON-NLS-1$
118 		}
119 
120 		IMatcher candidateMatcher = NO_MATCH;
121 		try {
122 			candidateMatcher = PathMatcher.createPathMatcher(pattern,
123 					Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly);
124 		} catch (InvalidPatternException e) {
125 			// ignore: invalid patterns are silently ignored
126 		}
127 		this.matcher = candidateMatcher;
128 		this.pattern = pattern;
129 	}
130 
131 	/**
132 	 * Whether to match directories only
133 	 *
134 	 * @return {@code true} if the pattern should match directories only
135 	 * @since 4.3
136 	 */
137 	public boolean isDirOnly() {
138 		return dirOnly;
139 	}
140 
141 	/**
142 	 * Return the attributes.
143 	 *
144 	 * @return an unmodifiable list of attributes (never returns
145 	 *         <code>null</code>)
146 	 */
147 	public List<Attribute> getAttributes() {
148 		return Collections.unmodifiableList(attributes);
149 	}
150 
151 	/**
152 	 * Whether the pattern is only a file name and not a path
153 	 *
154 	 * @return <code>true</code> if the pattern is just a file name and not a
155 	 *         path
156 	 */
157 	public boolean isNameOnly() {
158 		return nameOnly;
159 	}
160 
161 	/**
162 	 * Get the pattern
163 	 *
164 	 * @return The blob pattern to be used as a matcher (never returns
165 	 *         <code>null</code>)
166 	 */
167 	public String getPattern() {
168 		return pattern;
169 	}
170 
171 	/**
172 	 * Returns <code>true</code> if a match was made.
173 	 *
174 	 * @param relativeTarget
175 	 *            Name pattern of the file, relative to the base directory of
176 	 *            this rule
177 	 * @param isDirectory
178 	 *            Whether the target file is a directory or not
179 	 * @return True if a match was made.
180 	 */
181 	public boolean isMatch(String relativeTarget, boolean isDirectory) {
182 		if (relativeTarget == null)
183 			return false;
184 		if (relativeTarget.length() == 0)
185 			return false;
186 		boolean match = matcher.matches(relativeTarget, isDirectory, true);
187 		return match;
188 	}
189 
190 	/** {@inheritDoc} */
191 	@Override
192 	public String toString() {
193 		StringBuilder sb = new StringBuilder();
194 		sb.append(pattern);
195 		for (Attribute a : attributes) {
196 			sb.append(" "); //$NON-NLS-1$
197 			sb.append(a);
198 		}
199 		return sb.toString();
200 
201 	}
202 }