Add the new class Attributes holding multiple Attribute(s)

Attributes represents a semantic collector of Attribute(s) and replaces
the anonymous Map<String,Attribute>. This class will be returned by
TreeWalk.getAttributes(). It offers convenient access to the attributes
wrapped in the Attributes object. Adds preparations for a future
Attribute Macro Expansion

Change-Id: I8348c8c457a2a7f1f0c48050e10399b0fa1cdbe1
Signed-off-by: Ivan Motsch <ivan.motsch@bsiag.com>
This commit is contained in:
Ivan Motsch 2015-11-17 13:32:20 +01:00
parent adbe900683
commit 75697adc5a
13 changed files with 306 additions and 60 deletions

View File

@ -52,9 +52,7 @@
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.attributes.Attribute.State;
@ -258,12 +256,14 @@ private void assertAttributesNode(String pathName,
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else {
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
attributesNode.getAttributes(pathName, false, entryAttributes);
Attributes entryAttributes = new Attributes();
attributesNode.getAttributes(pathName,
false, entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) {
assertThat(entryAttributes.values(), hasItem(attribute));
assertThat(entryAttributes.getAll(),
hasItem(attribute));
}
} else {
assertTrue(

View File

@ -49,10 +49,6 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.junit.After;
import org.junit.Test;
@ -104,8 +100,8 @@ public void testNegativePattern() throws IOException {
is = new ByteArrayInputStream(attributeFileContent.getBytes());
AttributesNode node = new AttributesNode();
node.parse(is);
assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
assertAttribute("file.type1", node, new Attributes());
assertAttribute("file.type2", node, new Attributes());
}
@Test
@ -115,7 +111,7 @@ public void testEmptyNegativeAttributeKey() throws IOException {
is = new ByteArrayInputStream(attributeFileContent.getBytes());
AttributesNode node = new AttributesNode();
node.parse(is);
assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
assertAttribute("file.type1", node, new Attributes());
assertAttribute("file.type2", node, asSet(A_UNSET_ATTR));
}
@ -127,8 +123,8 @@ public void testEmptyValueKey() throws IOException {
is = new ByteArrayInputStream(attributeFileContent.getBytes());
AttributesNode node = new AttributesNode();
node.parse(is);
assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
assertAttribute("file.type1", node, new Attributes());
assertAttribute("file.type2", node, new Attributes());
assertAttribute("file.type3", node, asSet(new Attribute("attr", "")));
}
@ -166,17 +162,14 @@ public void testTabSeparator() throws IOException {
}
private void assertAttribute(String path, AttributesNode node,
Set<Attribute> attrs) {
HashMap<String, Attribute> attributes = new HashMap<String, Attribute>();
Attributes attrs) {
Attributes attributes = new Attributes();
node.getAttributes(path, false, attributes);
assertEquals(attrs, new HashSet<Attribute>(attributes.values()));
assertEquals(attrs, attributes);
}
static Set<Attribute> asSet(Attribute... attrs) {
Set<Attribute> result = new HashSet<Attribute>();
for (Attribute attr : attrs)
result.add(attr);
return result;
static Attributes asSet(Attribute... attrs) {
return new Attributes(attrs);
}
}

View File

@ -53,9 +53,7 @@
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.attributes.Attribute.State;
import org.eclipse.jgit.errors.CorruptObjectException;
@ -227,12 +225,14 @@ private void assertAttributesNode(String pathName,
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else {
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
attributesNode.getAttributes(pathName, false, entryAttributes);
Attributes entryAttributes = new Attributes();
attributesNode.getAttributes(pathName,
false, entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) {
assertThat(entryAttributes.values(), hasItem(attribute));
assertThat(entryAttributes.getAll(),
hasItem(attribute));
}
} else {
assertTrue(

View File

@ -802,8 +802,9 @@ private void assertEntry(FileMode type, String pathName,
assertEquals(type, walk.getFileMode(0));
assertEquals(checkinAttributes,
asSet(ci_walk.getAttributes().values()));
assertEquals(checkoutAttributes, asSet(walk.getAttributes().values()));
asSet(ci_walk.getAttributes().getAll()));
assertEquals(checkoutAttributes,
asSet(walk.getAttributes().getAll()));
if (D.equals(type)) {
walk.enterSubtree();

View File

@ -1,5 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.jgit" version="2">
<resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode">
<filter comment="attributes weren't really usable in earlier versions" id="338792546">
<message_arguments>
<message_argument value="org.eclipse.jgit.attributes.AttributesNode"/>
<message_argument value="getAttributes(String, boolean, Map&lt;String,Attribute&gt;)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/lib/BitmapIndex.java" type="org.eclipse.jgit.lib.BitmapIndex$BitmapBuilder">
<filter comment="interface is implemented by extenders but not clients of the API" id="403804204">
<message_arguments>

View File

@ -50,8 +50,10 @@
* <li>Set - represented by {@link State#SET}</li>
* <li>Unset - represented by {@link State#UNSET}</li>
* <li>Set to a value - represented by {@link State#CUSTOM}</li>
* <li>Unspecified - <code>null</code> is used instead of an instance of this
* class</li>
* <li>Unspecified - used to revert an attribute . This is crucial in order to
* mark an attribute as unspecified in the attributes map and thus preventing
* following (with lower priority) nodes from setting the attribute to a value
* at all</li>
* </ul>
* </p>
*
@ -61,6 +63,7 @@ public final class Attribute {
/**
* The attribute value state
* see also https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
*/
public static enum State {
/** the attribute is set */
@ -69,6 +72,13 @@ public static enum State {
/** the attribute is unset */
UNSET,
/**
* the attribute appears as if it would not be defined at all
*
* @since 4.2
*/
UNSPECIFIED,
/** the attribute is set to a custom value */
CUSTOM
}
@ -176,6 +186,8 @@ public String toString() {
return key;
case UNSET:
return "-" + key; //$NON-NLS-1$
case UNSPECIFIED:
return "!" + key; //$NON-NLS-1$
case CUSTOM:
default:
return key + "=" + value; //$NON-NLS-1$

View File

@ -0,0 +1,202 @@
/*
* Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.attributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.jgit.attributes.Attribute.State;
/**
* Represents a set of attributes for a path
* <p>
*
* @since 4.2
*/
public final class Attributes {
private final Map<String, Attribute> map = new LinkedHashMap<>();
/**
* Creates a new instance
*
* @param attributes
*/
public Attributes(Attribute... attributes) {
if (attributes != null) {
for (Attribute a : attributes) {
put(a);
}
}
}
/**
* @return true if the set does not contain any attributes
*/
public boolean isEmpty() {
return map.isEmpty();
}
/**
* @param key
* @return the attribute or null
*/
public Attribute get(String key) {
return map.get(key);
}
/**
* @return all attributes
*/
public Collection<Attribute> getAll() {
return new ArrayList<>(map.values());
}
/**
* @param a
*/
public void put(Attribute a) {
map.put(a.getKey(), a);
}
/**
* @param key
*/
public void remove(String key) {
map.remove(key);
}
/**
* @param key
* @return true if the {@link Attributes} contains this key
*/
public boolean containsKey(String key) {
return map.containsKey(key);
}
/**
* Returns the state.
*
* @param key
*
* @return the state (never returns <code>null</code>)
*/
public Attribute.State getState(String key) {
Attribute a = map.get(key);
return a != null ? a.getState() : Attribute.State.UNSPECIFIED;
}
/**
* @param key
* @return true if the key is {@link State#SET}, false in all other cases
*/
public boolean isSet(String key) {
return (getState(key) == State.SET);
}
/**
* @param key
* @return true if the key is {@link State#UNSET}, false in all other cases
*/
public boolean isUnset(String key) {
return (getState(key) == State.UNSET);
}
/**
* @param key
* @return true if the key is {@link State#UNSPECIFIED}, false in all other
* cases
*/
public boolean isUnspecified(String key) {
return (getState(key) == State.UNSPECIFIED);
}
/**
* @param key
* @return true if the key is {@link State#CUSTOM}, false in all other cases
* see {@link #getValue(String)} for the value of the key
*/
public boolean isCustom(String key) {
return (getState(key) == State.CUSTOM);
}
/**
* @param key
* @return the attribute value (may be <code>null</code>)
*/
public String getValue(String key) {
Attribute a = map.get(key);
return a != null ? a.getValue() : null;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append("["); //$NON-NLS-1$
buf.append(" "); //$NON-NLS-1$
for (Attribute a : map.values()) {
buf.append(a.toString());
buf.append(" "); //$NON-NLS-1$
}
buf.append("]"); //$NON-NLS-1$
return buf.toString();
}
@Override
public int hashCode() {
return map.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Attributes))
return false;
Attributes other = (Attributes) obj;
return this.map.equals(other.map);
}
}

View File

@ -50,7 +50,6 @@
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.eclipse.jgit.lib.Constants;
@ -134,11 +133,12 @@ public List<AttributesRule> getRules() {
* true if the target item is a directory.
* @param attributes
* Map that will hold the attributes matching this entry path. If
* it is not empty, this method will NOT override any
* existing entry.
* it is not empty, this method will NOT override any existing
* entry.
* @since 4.2
*/
public void getAttributes(String entryPath, boolean isDirectory,
Map<String, Attribute> attributes) {
public void getAttributes(String entryPath,
boolean isDirectory, Attributes attributes) {
// Parse rules in the reverse order that they were read since the last
// entry should be used
ListIterator<AttributesRule> ruleIterator = rules.listIterator(rules
@ -153,7 +153,7 @@ public void getAttributes(String entryPath, boolean isDirectory,
while (attributeIte.hasPrevious()) {
Attribute attr = attributeIte.previous();
if (!attributes.containsKey(attr.getKey()))
attributes.put(attr.getKey(), attr);
attributes.put(attr);
}
}
}

View File

@ -42,8 +42,6 @@
*/
package org.eclipse.jgit.attributes;
import java.util.Map;
/**
* Interface for classes which provide git attributes
*
@ -51,7 +49,7 @@
*/
public interface AttributesProvider {
/**
* @return the currently active attributes by attribute key
* @return the currently active attributes
*/
public Map<String, Attribute> getAttributes();
public Attributes getAttributes();
}

View File

@ -84,6 +84,13 @@ private static List<Attribute> parseAttributes(String attributesLine) {
continue;
}
if (attribute.startsWith("!")) {//$NON-NLS-1$
if (attribute.length() > 1)
result.add(new Attribute(attribute.substring(1),
State.UNSPECIFIED));
continue;
}
final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$
if (equalsIndex == -1)
result.add(new Attribute(attribute, State.SET));
@ -200,4 +207,16 @@ public boolean isMatch(String relativeTarget, boolean isDirectory) {
boolean match = matcher.matches(relativeTarget, isDirectory);
return match;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(pattern);
for (Attribute a : attributes) {
sb.append(" "); //$NON-NLS-1$
sb.append(a);
}
return sb.toString();
}
}

View File

@ -47,6 +47,7 @@
import java.io.IOException;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
@ -71,7 +72,7 @@ public AttributesNode load() throws IOException {
FS fs = repository.getFS();
File attributes = fs.resolve(repository.getDirectory(),
"info/attributes"); //$NON-NLS-1$
Constants.INFO_ATTRIBUTES);
FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
return r.getRules().isEmpty() ? null : r;

View File

@ -272,6 +272,13 @@ public final class Constants {
*/
public static final String INFO_EXCLUDE = "info/exclude";
/**
* Attributes-override-file
*
* @since 4.2
*/
public static final String INFO_ATTRIBUTES = "info/attributes";
/**
* The system property that contains the system user name
*

View File

@ -45,17 +45,16 @@
package org.eclipse.jgit.treewalk;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.attributes.AttributesProvider;
import org.eclipse.jgit.attributes.Attribute.State;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@ -258,7 +257,7 @@ public static TreeWalk forPath(final Repository db, final String path,
AbstractTreeIterator currentHead;
/** Cached attribute for the current entry */
private Map<String, Attribute> attrs = null;
private Attributes attrs = null;
/**
* Create a new tree walker for a given repository.
@ -1119,7 +1118,7 @@ static String pathOf(final byte[] buf, int pos, int end) {
* @return a {@link Set} of {@link Attribute}s that match the current entry.
* @since 4.2
*/
public Map<String, Attribute> getAttributes() {
public Attributes getAttributes() {
if (attrs != null)
return attrs;
@ -1138,35 +1137,42 @@ public Map<String, Attribute> getAttributes() {
&& other == null) {
// Can not retrieve the attributes without at least one of the above
// iterators.
return Collections.<String, Attribute> emptyMap();
return new Attributes();
}
String path = currentHead.getEntryPathString();
final boolean isDir = FileMode.TREE.equals(currentHead.mode);
Map<String, Attribute> attributes = new LinkedHashMap<String, Attribute>();
Attributes attributes = new Attributes();
try {
// Gets the info attributes
// Gets the global attributes node
AttributesNode globalNodeAttr = attributesNodeProvider
.getGlobalAttributesNode();
// Gets the info attributes node
AttributesNode infoNodeAttr = attributesNodeProvider
.getInfoAttributesNode();
// Gets the info attributes
if (infoNodeAttr != null) {
infoNodeAttr.getAttributes(path, isDir, attributes);
}
// Gets the attributes located on the current entry path
getPerDirectoryEntryAttributes(path, isDir, operationType,
workingTreeIterator, dirCacheIterator, other,
attributes);
workingTreeIterator, dirCacheIterator, other, attributes);
// Gets the attributes located in the global attribute file
AttributesNode globalNodeAttr = attributesNodeProvider
.getGlobalAttributesNode();
if (globalNodeAttr != null) {
globalNodeAttr.getAttributes(path, isDir, attributes);
}
} catch (IOException e) {
throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$
}
// now after all attributes are collected - in the correct hierarchy
// order - remove all unspecified entries (the ! marker)
for (Attribute a : attributes.getAll()) {
if (a.getState() == State.UNSPECIFIED)
attributes.remove(a.getKey());
}
return attributes;
}
@ -1195,7 +1201,7 @@ public Map<String, Attribute> getAttributes() {
private void getPerDirectoryEntryAttributes(String path, boolean isDir,
OperationType opType, WorkingTreeIterator workingTreeIterator,
DirCacheIterator dirCacheIterator, CanonicalTreeParser other,
Map<String, Attribute> attributes)
Attributes attributes)
throws IOException {
// Prevents infinite recurrence
if (workingTreeIterator != null || dirCacheIterator != null
@ -1208,12 +1214,11 @@ private void getPerDirectoryEntryAttributes(String path, boolean isDir,
getPerDirectoryEntryAttributes(path, isDir, opType,
getParent(workingTreeIterator, WorkingTreeIterator.class),
getParent(dirCacheIterator, DirCacheIterator.class),
getParent(other, CanonicalTreeParser.class),
attributes);
getParent(other, CanonicalTreeParser.class), attributes);
}
}
private <T extends AbstractTreeIterator> T getParent(T current,
private static <T extends AbstractTreeIterator> T getParent(T current,
Class<T> type) {
if (current != null) {
AbstractTreeIterator parent = current.parent;
@ -1224,7 +1229,7 @@ private <T extends AbstractTreeIterator> T getParent(T current,
return null;
}
private <T> T getTree(Class<T> type) {
private <T extends AbstractTreeIterator> T getTree(Class<T> type) {
for (int i = 0; i < trees.length; i++) {
AbstractTreeIterator tree = trees[i];
if (type.isInstance(tree)) {