Work on #30, still not working though...

This commit is contained in:
James Agnew 2014-09-26 17:16:37 -04:00
parent 1d084fe6b0
commit 0d6eca70a9
6 changed files with 356 additions and 26 deletions

View File

@ -7,6 +7,12 @@
</properties> </properties>
<body> <body>
<release version="0.7" date="TBD"> <release version="0.7" date="TBD">
<action type="add" issue="30">
<![CDATA[<b>API CHANGE:</b>]]> The TagList class previously implemented ArrayList semantics,
but this has been replaced with LinkedHashMap semantics. This means that the list of
tags will no longer accept duplicate tags, but that tag order will still be
preserved. Thanks to Bill de Beaubien for reporting!
</action>
<action type="add" dev="suranga"> <action type="add" dev="suranga">
Documentation fixes Documentation fixes
</action> </action>

View File

@ -29,28 +29,25 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
public class Tag extends BaseElement implements IElement { public class Tag extends BaseElement implements IElement {
/**
* Convenience constant containing the "http://hl7.org/fhir/tag" scheme public static final String ATTR_LABEL = "label";
* value public static final String ATTR_SCHEME = "scheme";
*/ public static final String ATTR_TERM = "term";
public static final String HL7_ORG_FHIR_TAG = "http://hl7.org/fhir/tag";
/** /**
* Convenience constant containing the "http://hl7.org/fhir/tag/security" scheme * Convenience constant containing the "http://hl7.org/fhir/tag" scheme value
* value */
public static final String HL7_ORG_FHIR_TAG = "http://hl7.org/fhir/tag";
/**
* Convenience constant containing the "http://hl7.org/fhir/tag/profile" scheme value
*/
public static final String HL7_ORG_PROFILE_TAG = "http://hl7.org/fhir/tag/profile";
/**
* Convenience constant containing the "http://hl7.org/fhir/tag/security" scheme value
*/ */
public static final String HL7_ORG_SECURITY_TAG = "http://hl7.org/fhir/tag/security"; public static final String HL7_ORG_SECURITY_TAG = "http://hl7.org/fhir/tag/security";
/** private transient Integer myHashCode;
* Convenience constant containing the "http://hl7.org/fhir/tag/profile" scheme
* value
*/
public static final String HL7_ORG_PROFILE_TAG = "http://hl7.org/fhir/tag/profile";
public static final String ATTR_TERM = "term";
public static final String ATTR_LABEL = "label";
public static final String ATTR_SCHEME = "scheme";
private String myLabel; private String myLabel;
private String myScheme; private String myScheme;
private String myTerm; private String myTerm;
@ -119,11 +116,17 @@ public class Tag extends BaseElement implements IElement {
@Override @Override
public int hashCode() { public int hashCode() {
if (myHashCode != null) {
System.out.println("Tag alread has hashcode " + myHashCode + " - " + myScheme + " - " + myTerm + " - " + myLabel);
return myHashCode;
}
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + ((myLabel == null) ? 0 : myLabel.hashCode()); result = prime * result + ((myLabel == null) ? 0 : myLabel.hashCode());
result = prime * result + ((myScheme == null) ? 0 : myScheme.hashCode()); result = prime * result + ((myScheme == null) ? 0 : myScheme.hashCode());
result = prime * result + ((myTerm == null) ? 0 : myTerm.hashCode()); result = prime * result + ((myTerm == null) ? 0 : myTerm.hashCode());
myHashCode = result;
System.out.println("Tag has hashcode " + myHashCode + " - " + myScheme + " - " + myTerm + " - " + myLabel);
return result; return result;
} }
@ -134,16 +137,19 @@ public class Tag extends BaseElement implements IElement {
public Tag setLabel(String theLabel) { public Tag setLabel(String theLabel) {
myLabel = theLabel; myLabel = theLabel;
myHashCode = null;
return this; return this;
} }
public Tag setScheme(String theScheme) { public Tag setScheme(String theScheme) {
myScheme = theScheme; myScheme = theScheme;
myHashCode = null;
return this; return this;
} }
public Tag setTerm(String theTerm) { public Tag setTerm(String theTerm) {
myTerm = theTerm; myTerm = theTerm;
myHashCode = null;
return this; return this;
} }

View File

@ -20,26 +20,193 @@ package ca.uhn.fhir.model.api;
* #L% * #L%
*/ */
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
public class TagList extends ArrayList<Tag> { /**
* A collection of tags present on a single resource. TagList is backed by a {@link LinkedHashSet}, so the order of added tags will be consistent, but duplicates will not be preserved.
*
* <p>
* <b>Thread safety:</b> This class is not thread safe
* </p>
*/
public class TagList implements Set<Tag>, Serializable {
private static final long serialVersionUID = 1L;
public static final String ATTR_CATEGORY = "category"; public static final String ATTR_CATEGORY = "category";
public static final String ELEMENT_NAME = "TagList"; public static final String ELEMENT_NAME = "TagList";
public static final String ELEMENT_NAME_LC = ELEMENT_NAME.toLowerCase(); public static final String ELEMENT_NAME_LC = ELEMENT_NAME.toLowerCase();
private static final long serialVersionUID = 1L;
private transient List<Tag> myOrderedTags;
private LinkedHashSet<Tag> myTagSet = new LinkedHashSet<Tag>();
/**
* Constructor
*/
public TagList() {
super();
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("TagList[").append(size()).append(" tag(s)]");
for (Tag next : this) {
b.append("\n * ").append(next.toString());
}
return b.toString();
}
@Override
public boolean add(Tag theE) {
myOrderedTags = null;
return myTagSet.add(theE);
}
@Override
public boolean addAll(Collection<? extends Tag> theC) {
myOrderedTags = null;
return myTagSet.addAll(theC);
}
public Tag addTag() {
myOrderedTags = null;
return addTag(null, null, null);
}
public Tag addTag(String theScheme, String theTerm, String theLabel) { public Tag addTag(String theScheme, String theTerm, String theLabel) {
Tag retVal = new Tag(theScheme, theTerm, theLabel); Tag retVal = new Tag(theScheme, theTerm, theLabel);
add(retVal); add(retVal);
myOrderedTags = null;
return retVal; return retVal;
} }
public Tag addTag() { @Override
return addTag(null, null, null); public void clear() {
myOrderedTags = null;
myTagSet.clear();
} }
@Override
public boolean contains(Object theO) {
return myTagSet.contains(theO);
}
@Override
public boolean containsAll(Collection<?> theC) {
return myTagSet.containsAll(theC);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TagList other = (TagList) obj;
if (myTagSet == null) {
if (other.myTagSet != null)
return false;
} else if (!myTagSet.equals(other.myTagSet))
return false;
return true;
}
/**
* Returns the tag at a given index - Note that the TagList is backed by a {@link LinkedHashSet}, so the order of added tags will be consistent, but duplicates will not be preserved.
*/
public Tag get(int theIndex) {
if (myOrderedTags == null) {
myOrderedTags = new ArrayList<Tag>();
for (Tag next : myTagSet) {
myOrderedTags.add(next);
}
}
return myOrderedTags.get(theIndex);
}
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public Tag set(int theIndex, Tag theElement) {
// throw new UnsupportedOperationException();
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public void add(int theIndex, Tag theElement) {
// myTagSet.add(theElement);
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public Tag remove(int theIndex) {
// Tag retVal = myTagSet.remove(theIndex);
// myTagSet.remove(retVal);
// return retVal;
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public int indexOf(Object theO) {
// myTagSet.remove(theO);
// return myTagSet.indexOf(theO);
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public int lastIndexOf(Object theO) {
// return myTagSet.lastIndexOf(theO);
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public ListIterator<Tag> listIterator() {
// return myTagSet.listIterator();
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public ListIterator<Tag> listIterator(int theIndex) {
// return myTagSet.listIterator(theIndex);
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public List<Tag> subList(int theFromIndex, int theToIndex) {
// return myTagSet.subList(theFromIndex, theToIndex);
// }
public Tag getTag(String theScheme, String theTerm) { public Tag getTag(String theScheme, String theTerm) {
for (Tag next : this) { for (Tag next : this) {
if (theScheme.equals(next.getScheme()) && theTerm.equals(next.getTerm())) { if (theScheme.equals(next.getScheme()) && theTerm.equals(next.getTerm())) {
@ -59,4 +226,68 @@ public class TagList extends ArrayList<Tag> {
return retVal; return retVal;
} }
@Override
public int hashCode() {
return myTagSet.hashCode();
}
@Override
public boolean isEmpty() {
return myTagSet.isEmpty();
}
@Override
public Iterator<Tag> iterator() {
return myTagSet.iterator();
}
@Override
public boolean remove(Object theO) {
myOrderedTags = null;
return myTagSet.remove(theO);
}
@Override
public boolean removeAll(Collection<?> theC) {
myOrderedTags = null;
return myTagSet.removeAll(theC);
}
@Override
public boolean retainAll(Collection<?> theC) {
myOrderedTags = null;
return myTagSet.retainAll(theC);
}
@Override
public int size() {
return myTagSet.size();
}
@Override
public Object[] toArray() {
return myTagSet.toArray();
}
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
// public boolean addAll(int theIndex, Collection<? extends Tag> theC) {
// myTagSet.addAll(theC);
// return myTagSet.addAll(theC);
// }
//
// /**
// * @deprecated TagList will not implement the {@link List} interface in a future release, as tag order is not supposed to be meaningful
// */
// @Deprecated
// @Override
@Override
public <T> T[] toArray(T[] theA) {
return myTagSet.toArray(theA);
}
} }

View File

@ -29,7 +29,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;

View File

@ -0,0 +1,67 @@
package ca.uhn.fhir.model.api;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.resource.Patient;
public class TagListTest {
private FhirContext myCtx = new FhirContext();
@Test
public void testEquals() {
TagList tagList1 = new TagList();
tagList1.addTag(null, "Dog", "Puppies");
tagList1.addTag("http://foo", "Cat", "Kittens");
TagList tagList2 = new TagList();
tagList2.addTag(null, "Dog", "Puppies");
tagList2.addTag("http://foo", "Cat", "Kittens");
assertEquals(tagList1,tagList2);
}
@Test
public void testEqualsIgnoresOrder() {
TagList tagList1 = new TagList();
tagList1.addTag(null, "Dog", "Puppies");
tagList1.addTag("http://foo", "Cat", "Kittens");
TagList tagList2 = new TagList();
tagList2.addTag("http://foo", "Cat", "Kittens");
tagList2.addTag(null, "Dog", "Puppies");
assertEquals(tagList1,tagList2);
}
@Test
public void testPreventDuplication() {
Patient patient = new Patient();
patient.addIdentifier("urn:system", "testTagsWithCreateAndReadAndSearch");
patient.addName().addFamily("Tester").addGiven("Joe");
TagList tagList = new TagList();
tagList.addTag(null, "Dog", "Puppies");
// Add this twice
tagList.addTag("http://foo", "Cat", "Kittens");
tagList.addTag("http://foo", "Cat", "Kittens");
patient.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
Bundle b = new Bundle();
b.addResource(patient, myCtx, "http://foo");
String encoded = myCtx.newXmlParser().encodeBundleToString(b);
assertThat(encoded, stringContainsInOrder(Arrays.asList("Cat", "Kittens")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("Cat", "Kittens", "Cat", "Kittens"))));
}
}

View File

@ -1189,14 +1189,17 @@ public class FhirResourceDaoTest {
patient.addName().addFamily("Tester").addGiven("Joe"); patient.addName().addFamily("Tester").addGiven("Joe");
TagList tagList = new TagList(); TagList tagList = new TagList();
tagList.addTag(null, "Dog", "Puppies"); tagList.addTag(null, "Dog", "Puppies");
// Add this twice
tagList.addTag("http://foo", "Cat", "Kittens");
tagList.addTag("http://foo", "Cat", "Kittens"); tagList.addTag("http://foo", "Cat", "Kittens");
patient.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList); patient.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
MethodOutcome outcome = ourPatientDao.create(patient); MethodOutcome outcome = ourPatientDao.create(patient);
assertNotNull(outcome.getId()); IdDt patientId = outcome.getId();
assertFalse(outcome.getId().isEmpty()); assertNotNull(patientId);
assertFalse(patientId.isEmpty());
Patient retrieved = ourPatientDao.read(outcome.getId()); Patient retrieved = ourPatientDao.read(patientId);
TagList published = (TagList) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); TagList published = (TagList) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
assertEquals(2, published.size()); assertEquals(2, published.size());
assertEquals("Dog", published.get(0).getTerm()); assertEquals("Dog", published.get(0).getTerm());
@ -1217,6 +1220,23 @@ public class FhirResourceDaoTest {
assertEquals("Kittens", published.get(1).getLabel()); assertEquals("Kittens", published.get(1).getLabel());
assertEquals("http://foo", published.get(1).getScheme()); assertEquals("http://foo", published.get(1).getScheme());
ourPatientDao.addTag(patientId, "http://foo", "Cat", "Kittens");
ourPatientDao.addTag(patientId, "http://foo", "Cow", "Calves");
retrieved = ourPatientDao.read(patientId);
published = (TagList) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
assertEquals(3, published.size());
assertEquals("Dog", published.get(0).getTerm());
assertEquals("Puppies", published.get(0).getLabel());
assertEquals(null, published.get(0).getScheme());
assertEquals("Cat", published.get(1).getTerm());
assertEquals("Kittens", published.get(1).getLabel());
assertEquals("http://foo", published.get(1).getScheme());
assertEquals("Cow", published.get(2).getTerm());
assertEquals("Calves", published.get(2).getLabel());
assertEquals("http://foo", published.get(2).getScheme());
} }
@Test @Test