mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Jean-Noel Rouvignac
18.56.2015 f61444ce38af62d66efd549a90c9a958bde95691
Changed AttrHistorical.getValuesHistorical() return type to match usage


AttrHistorical.java:
In getValuesHistorical(), changed return type from Map<AttrValueHistorical, AttrValueHistorical> to Set<AttrValueHistorical> - to match the usage.

AttrHistoricalMultiple.java:
Consequence of the change to AttrHistorical.getValuesHistorical().
In delete(Attribute, CSN), now call delete(ByteString, CSN).
In add(Attribute, CSN), now call add(ByteString, CSN).
Extracted method update(CSN, AttrValueHistorical).
In conflictAdd(), changed return type to void since it was never used.
Removed javadoc copied from supertype + useless @inheritDoc.
Reduced method's visibilities.

AttrHistoricalSingle.java
Consequence of the change to AttrHistorical.getValuesHistorical().
Removed javadoc copied from supertype + useless @inheritDoc.
Used static import for HistAttrModificationKey.*.
Extracted method getSingleValue().

EntryHistorical.java:
Consequence of the change to AttrHistorical.getValuesHistorical().
Extracted method toOptionsString().
In newInstanceFromEntry(), reduced scope of variables.

AttrInfoTest.java:
Consequence of the change to AttrHistorical.getValuesHistorical().
5 files modified
341 ■■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistorical.java 33 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java 142 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalSingle.java 74 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/EntryHistorical.java 63 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrInfoTest.java 29 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistorical.java
@@ -28,17 +28,15 @@
package org.opends.server.replication.plugin;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.replication.common.CSN;
import org.opends.server.types.AttributeType;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
/**
 * This class store historical information for a provided attribute.
 */
/** This class store historical information for a provided attribute. */
public abstract class AttrHistorical
{
  /**
@@ -55,23 +53,21 @@
   * @return a boolean indicating if a conflict was detected.
   */
  public abstract boolean replayOperation(
      Iterator<Modification> modsIterator, CSN csn,
      Entry modifiedEntry, Modification mod);
      Iterator<Modification> modsIterator, CSN csn, Entry modifiedEntry, Modification mod);
  /**
   * This method calculate the historical information and update the hist
   * This method calculates the historical information and update the hist
   * attribute to store the historical information for modify operation that
   * does not conflict with previous operation.
   * This is the usual path and should therefore be optimized.
   *
   * <p>
   * It does not check if the operation to process is conflicting or not with
   * previous operations. The caller is responsible for this.
   *
   * @param csn The CSN of the operation to process
   * @param mod The modify operation to process.
   */
  public abstract void processLocalOrNonConflictModification(
      CSN csn, Modification mod);
  public abstract void processLocalOrNonConflictModification(CSN csn, Modification mod);
  /**
   * Create a new object from a provided attribute type. Historical is empty.
@@ -79,19 +75,17 @@
   * @param type the provided attribute type.
   * @return a new AttributeInfo object.
   */
  public static AttrHistorical createAttributeHistorical(
      AttributeType type)
  public static AttrHistorical createAttributeHistorical(AttributeType type)
  {
    return type.isSingleValue() ? new AttrHistoricalSingle() : new AttrHistoricalMultiple();
  }
  /**
   * Get the List of ValueInfo for this attribute Info.
   * Get the historical informations for this attribute Info.
   *
   * @return the List of ValueInfo
   * @return the historical informations
   */
  public abstract Map<AttrValueHistorical, AttrValueHistorical> getValuesHistorical();
  public abstract Set<AttrValueHistorical> getValuesHistorical();
  /**
   * Returns the last time when this attribute was deleted.
@@ -107,8 +101,5 @@
   * @param value   the associated value or null if there is no value;
   * @param csn     the associated CSN.
   */
  public abstract void assign(
      HistAttrModificationKey histKey, ByteString value, CSN csn);
  public abstract void assign(HistAttrModificationKey histKey, ByteString value, CSN csn);
}
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java
@@ -29,11 +29,16 @@
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
import org.opends.server.replication.common.CSN;
import org.opends.server.types.*;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
/**
 * This class is used to store historical information for multiple valued
@@ -98,10 +103,6 @@
     return lastUpdateTime;
   }
   /**
    * Returns the last time when the attribute was deleted.
    * @return the last time when the attribute was deleted
    */
   @Override
   public CSN getDeleteTime()
   {
@@ -117,8 +118,7 @@
    */
   AttrHistoricalMultiple duplicate()
   {
     return new AttrHistoricalMultiple(this.deleteTime, this.lastUpdateTime,
         this.valuesHist);
     return new AttrHistoricalMultiple(this.deleteTime, this.lastUpdateTime, this.valuesHist);
   }
   /**
@@ -127,7 +127,7 @@
    * Add the delete attribute state information
    * @param csn time when the delete was done
    */
   protected void delete(CSN csn)
   void delete(CSN csn)
   {
     // iterate through the values in the valuesInfo and suppress all the values
     // that have not been added after the date of this delete.
@@ -153,43 +153,46 @@
     }
   }
  /**
   * Update the historical of this attribute after deleting a set of values.
   *
   * @param attr
   *          the attribute containing the set of values that were deleted
   * @param csn
   *          time when the delete was done
   */
  void delete(Attribute attr, CSN csn)
  {
    for (ByteString val : attr)
    {
      delete(val, csn);
    }
  }
   /**
    * Update the historical of this attribute after a delete value.
    *
    * @param val value that was deleted
    * @param csn time when the delete was done
    */
   protected void delete(ByteString val, CSN csn)
   void delete(ByteString val, CSN csn)
   {
     AttrValueHistorical info = new AttrValueHistorical(val, null, csn);
     valuesHist.remove(info);
     valuesHist.put(info, info);
     if (csn.isNewerThan(lastUpdateTime))
     {
       lastUpdateTime = csn;
     }
     update(csn, new AttrValueHistorical(val, null, csn));
   }
   /**
     * Update the historical of this attribute after deleting a set of values.
     *
     * @param attr
     *          the attribute containing the set of values that were
     *          deleted
     * @param csn
     *          time when the delete was done
     */
  protected void delete(Attribute attr, CSN csn)
  /**
   * Update the historical information when values are added.
   *
   * @param attr
   *          the attribute containing the set of added values
   * @param csn
   *          time when the add is done
   */
  private void add(Attribute attr, CSN csn)
  {
    for (ByteString val : attr)
    {
      AttrValueHistorical info = new AttrValueHistorical(val, null, csn);
      valuesHist.remove(info);
      valuesHist.put(info, info);
      if (csn.isNewerThan(lastUpdateTime))
      {
        lastUpdateTime = csn;
      }
      add(val, csn);
    }
  }
@@ -201,51 +204,27 @@
     * @param csn
     *          time when the value was added
     */
   protected void add(ByteString addedValue, CSN csn)
   void add(ByteString addedValue, CSN csn)
   {
     AttrValueHistorical info = new AttrValueHistorical(addedValue, csn, null);
     valuesHist.remove(info);
     valuesHist.put(info, info);
     if (csn.isNewerThan(lastUpdateTime))
     {
       lastUpdateTime = csn;
     }
     update(csn, new AttrValueHistorical(addedValue, csn, null));
   }
   /**
     * Update the historical information when values are added.
     *
     * @param attr
     *          the attribute containing the set of added values
     * @param csn
     *          time when the add is done
     */
  private void add(Attribute attr, CSN csn)
  private void update(CSN csn, AttrValueHistorical info)
  {
    for (ByteString val : attr)
    valuesHist.remove(info);
    valuesHist.put(info, info);
    if (csn.isNewerThan(lastUpdateTime))
    {
      AttrValueHistorical info = new AttrValueHistorical(val, csn, null);
      valuesHist.remove(info);
      valuesHist.put(info, info);
      if (csn.isNewerThan(lastUpdateTime))
      {
        lastUpdateTime = csn;
      }
      lastUpdateTime = csn;
    }
  }
  /**
   * Get the list of historical information for the values.
   *
   * @return the list of historical information for the values.
   */
  @Override
  public Map<AttrValueHistorical,AttrValueHistorical> getValuesHistorical()
  public Set<AttrValueHistorical> getValuesHistorical()
  {
    return valuesHist;
    return valuesHist.keySet();
  }
  /** {@inheritDoc} */
  @Override
  public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn,
      Entry modifiedEntry, Modification m)
@@ -326,25 +305,12 @@
    }
  }
  /**
   * This method calculates the historical information and update the hist
   * attribute to store the historical information for a modify operation that
   * does not conflict with previous operation.
   * This is the usual path and should therefore be optimized.
   *
   * It does not check if the operation to process is conflicting or not with
   * previous operations. The caller is responsible for this.
   *
   * @param csn The CSN of the operation to process
   * @param mod The modify operation to process.
   */
  @Override
  public void processLocalOrNonConflictModification(CSN csn, Modification mod)
  {
    /*
     * The operation is either a non-conflicting operation or a local
     * operation so there is no need to check the historical information
     * for conflicts.
     * The operation is either a non-conflicting operation or a local operation
     * so there is no need to check the historical information for conflicts.
     * If this is a local operation, then this code is run after
     * the pre-operation phase.
     * If this is a non-conflicting replicated operation, this code is run
@@ -472,8 +438,7 @@
        boolean addedInCurrentOp = false;
        /* update historical information */
        AttrValueHistorical valInfo =
          new AttrValueHistorical(val, null, csn);
        AttrValueHistorical valInfo = new AttrValueHistorical(val, null, csn);
        AttrValueHistorical oldValInfo = valuesHist.get(valInfo);
        if (oldValInfo != null)
        {
@@ -539,10 +504,8 @@
   * @param csn  the historical info associated to the entry
   * @param m the modification that is being processed
   * @param modsIterator iterator on the list of modification
   * @return false if operation becomes empty and must not be processed
   */
  private boolean conflictAdd(CSN csn, Modification m,
      Iterator<Modification> modsIterator)
  private void conflictAdd(CSN csn, Modification m, Iterator<Modification> modsIterator)
  {
    /*
     * if historicalattributedelete is newer forget this mod else find
@@ -560,7 +523,7 @@
       * forget this MOD ADD
       */
      modsIterator.remove();
      return false;
      return;
    }
    AttributeBuilder builder = new AttributeBuilder(m.getAttribute());
@@ -633,11 +596,8 @@
    {
      lastUpdateTime = csn;
    }
    return true;
  }
  /** {@inheritDoc} */
  @Override
  public void assign(HistAttrModificationKey histKey, ByteString value, CSN csn)
  {
@@ -671,5 +631,3 @@
    }
  }
}
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalSingle.java
@@ -26,9 +26,11 @@
 */
package org.opends.server.replication.plugin;
import static org.opends.server.replication.plugin.HistAttrModificationKey.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
@@ -54,45 +56,33 @@
  private CSN addTime;
  /** Last added value. */
  private ByteString value;
  /**
   * Last operation applied. This is only used for multiple mods on the same
   * single valued attribute in the same modification.
   */
  private HistAttrModificationKey lastMod;
  /** {@inheritDoc} */
  @Override
  public CSN getDeleteTime()
  {
    return this.deleteTime;
  }
  /** {@inheritDoc} */
  @Override
  public Map<AttrValueHistorical,AttrValueHistorical> getValuesHistorical()
  public Set<AttrValueHistorical> getValuesHistorical()
  {
    if (addTime == null)
    if (addTime != null)
    {
      return Collections.emptyMap();
      return Collections.singleton(new AttrValueHistorical(value, addTime, null));
    }
    else
    {
      AttrValueHistorical val = new AttrValueHistorical(value, addTime, null);
      return Collections.singletonMap(val, val);
    }
    return Collections.emptySet();
  }
  /** {@inheritDoc} */
  @Override
  public void processLocalOrNonConflictModification(CSN csn, Modification mod)
  {
    ByteString newValue = null;
    Attribute modAttr = mod.getAttribute();
    if (modAttr != null && !modAttr.isEmpty())
    {
      newValue = modAttr.iterator().next();
    }
    ByteString newValue = getSingleValue(modAttr);
    switch (mod.getModificationType().asEnum())
    {
@@ -100,13 +90,13 @@
      this.addTime = null;
      this.deleteTime = csn;
      this.value = newValue;
      lastMod = HistAttrModificationKey.DEL;
      lastMod = DEL;
      break;
    case ADD:
      this.addTime = csn;
      this.value = newValue;
      lastMod = HistAttrModificationKey.ADD;
      lastMod = ADD;
      break;
    case REPLACE:
@@ -116,12 +106,12 @@
        this.addTime = null;
        this.deleteTime = csn;
        this.value = null;
        lastMod = HistAttrModificationKey.DEL;
        lastMod = DEL;
      }
      else
      {
        this.deleteTime = addTime = csn;
        lastMod = HistAttrModificationKey.REPL;
        lastMod = REPL;
      }
      this.value = newValue;
      break;
@@ -132,20 +122,14 @@
    }
  }
  /** {@inheritDoc} */
  @Override
  public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn,
      Entry modifiedEntry, Modification mod)
  {
    boolean conflict = false;
    ByteString newValue = null;
    Attribute modAttr = mod.getAttribute();
    if (modAttr != null && !modAttr.isEmpty())
    {
      newValue = modAttr.iterator().next();
    }
    ByteString newValue = getSingleValue(modAttr);
    boolean conflict = false;
    switch (mod.getModificationType().asEnum())
    {
    case DELETE:
@@ -172,7 +156,7 @@
          else
          {
            addTime = null;
            lastMod = HistAttrModificationKey.DEL;
            lastMod = DEL;
            value = null;
          }
        }
@@ -184,15 +168,14 @@
      }
      else if (csn.equals(addTime))
      {
        if (lastMod == HistAttrModificationKey.ADD
            || lastMod == HistAttrModificationKey.REPL)
        if (lastMod == ADD || lastMod == REPL)
        {
          if (csn.isNewerThan(deleteTime))
          {
            deleteTime = csn;
          }
          addTime = null;
          lastMod = HistAttrModificationKey.DEL;
          lastMod = DEL;
          value = null;
        }
        else
@@ -215,7 +198,7 @@
        mod.setModificationType(ModificationType.REPLACE);
        addTime = csn;
        value = newValue;
        lastMod = HistAttrModificationKey.REPL;
        lastMod = REPL;
      }
      else
      {
@@ -225,17 +208,17 @@
          // no conflict : don't do anything beside setting the addTime
          addTime = csn;
          value = newValue;
          lastMod = HistAttrModificationKey.ADD;
          lastMod = ADD;
        }
        else
        {
          // Case where CSN = addTime = deleteTime
          if (csn.equals(deleteTime) && csn.equals(addTime)
              && lastMod == HistAttrModificationKey.DEL)
              && lastMod == DEL)
          {
            // No conflict, record the new value.
            value = newValue;
            lastMod = HistAttrModificationKey.ADD;
            lastMod = ADD;
          }
          else
          {
@@ -260,14 +243,14 @@
          addTime = null;
          value = newValue;
          deleteTime = csn;
          lastMod = HistAttrModificationKey.DEL;
          lastMod = DEL;
        }
        else
        {
          addTime = csn;
          value = newValue;
          deleteTime = csn;
          lastMod = HistAttrModificationKey.REPL;
          lastMod = REPL;
        }
      }
      break;
@@ -279,7 +262,15 @@
    return conflict;
  }
  /** {@inheritDoc} */
  private ByteString getSingleValue(Attribute modAttr)
  {
    if (modAttr != null && !modAttr.isEmpty())
    {
      return modAttr.iterator().next();
    }
    return null;
  }
  @Override
  public void assign(HistAttrModificationKey histKey, ByteString value, CSN csn)
  {
@@ -312,4 +303,3 @@
    }
  }
}
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/EntryHistorical.java
@@ -433,30 +433,17 @@
      Map<Set<String>, AttrHistorical> attrWithOptions =
                                entryWithOptions.getValue().getAttributesInfo();
      for (Map.Entry<Set<String>, AttrHistorical> entry : attrWithOptions
          .entrySet())
      for (Map.Entry<Set<String>, AttrHistorical> entry : attrWithOptions.entrySet())
      {
        // Encode an (attribute type/option)
        Set<String> options = entry.getKey();
        String optionsString = toOptionsString(entry.getKey());
        AttrHistorical attrHist = entry.getValue();
        String optionsString = "";
        if (options != null)
        {
          StringBuilder optionsBuilder = new StringBuilder();
          for (String s : options)
          {
            optionsBuilder.append(';').append(s);
          }
          optionsString = optionsBuilder.toString();
        }
        CSN deleteTime = attrHist.getDeleteTime();
        /* generate the historical information for deleted attributes */
        boolean delAttr = deleteTime != null;
        for (AttrValueHistorical attrValHist : attrHist.getValuesHistorical()
            .keySet())
        for (AttrValueHistorical attrValHist : attrHist.getValuesHistorical())
        {
          final ByteString value = attrValHist.getAttributeValue();
@@ -534,6 +521,20 @@
    return builder.toAttribute();
  }
  private String toOptionsString(Set<String> options)
  {
    if (options != null)
    {
      StringBuilder optionsBuilder = new StringBuilder();
      for (String s : options)
      {
        optionsBuilder.append(';').append(s);
      }
      return optionsBuilder.toString();
    }
    return "";
  }
  private boolean needsPurge(CSN csn, long purgeDate)
  {
    boolean needsPurge = purgeDelayInMillisec > 0 && csn.getTime() <= purgeDate;
@@ -623,17 +624,11 @@
   */
  public static EntryHistorical newInstanceFromEntry(Entry entry)
  {
    AttributeType lastAttrType = null;
    Set<String> lastOptions = new HashSet<>();
    AttrHistorical attrInfo = null;
    AttrHistoricalWithOptions attrInfoWithOptions = null;
    // Read the DB historical attribute from the entry
    List<Attribute> histAttrWithOptionsFromEntry = getHistoricalAttr(entry);
    // Now we'll build the Historical object we want to construct
    EntryHistorical newHistorical = new EntryHistorical();
    final EntryHistorical newHistorical = new EntryHistorical();
    if (histAttrWithOptionsFromEntry == null)
    {
      // No historical attribute in the entry, return empty object
@@ -642,6 +637,11 @@
    try
    {
      AttributeType lastAttrType = null;
      Set<String> lastOptions = new HashSet<>();
      AttrHistorical attrInfo = null;
      AttrHistoricalWithOptions attrInfoWithOptions = null;
      // For each value of the historical attr read (mod. on a user attribute)
      //   build an AttrInfo sub-object
@@ -653,14 +653,8 @@
        for (ByteString histAttrValueFromEntry : histAttrFromEntry)
        {
          // From each value of the hist attr, create an object
          HistoricalAttributeValue histVal = new HistoricalAttributeValue(
              histAttrValueFromEntry.toString());
          AttributeType attrType = histVal.getAttrType();
          Set<String> options = histVal.getOptions();
          CSN csn = histVal.getCSN();
          ByteString value = histVal.getAttributeValue();
          HistAttrModificationKey histKey = histVal.getHistKey();
          final HistoricalAttributeValue histVal = new HistoricalAttributeValue(histAttrValueFromEntry.toString());
          final CSN csn = histVal.getCSN();
          // update the oldest CSN stored in the new entry historical
          newHistorical.updateOldestCSN(csn);
@@ -675,6 +669,7 @@
          }
          else
          {
            AttributeType attrType = histVal.getAttrType();
            if (attrType == null)
            {
              /*
@@ -694,6 +689,7 @@
             *   AttrInfo that we add to AttrInfoWithOptions
             * if both match we keep everything
             */
            Set<String> options = histVal.getOptions();
            if (attrType != lastAttrType)
            {
              attrInfo = AttrHistorical.createAttributeHistorical(attrType);
@@ -703,8 +699,7 @@
              attrInfoWithOptions.put(options, attrInfo);
              // Store this attrInfoWithOptions in the newHistorical object
              newHistorical.attributesHistorical.
                put(attrType, attrInfoWithOptions);
              newHistorical.attributesHistorical.put(attrType, attrInfoWithOptions);
              lastAttrType = attrType;
              lastOptions = options;
@@ -716,7 +711,7 @@
              lastOptions = options;
            }
            attrInfo.assign(histKey, value, csn);
            attrInfo.assign(histVal.getHistKey(), histVal.getAttributeValue(), csn);
          }
        }
      }
opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrInfoTest.java
@@ -27,13 +27,13 @@
package org.opends.server.replication.plugin;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.CSN;
import org.opends.server.types.AttributeType;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.types.Attributes;
import org.opends.server.util.TimeThread;
import org.testng.annotations.DataProvider;
@@ -41,16 +41,11 @@
import static org.testng.Assert.*;
/**
 * Test AttrInfo and AttrInfoWithOptions.
 */
/** Test AttrInfo and AttrInfoWithOptions. */
@SuppressWarnings("javadoc")
public class AttrInfoTest extends ReplicationTestCase
{
  /**
   * Build some data for the AttrInfo test below.
   */
  /** Build some data for the AttrInfo test below. */
  @DataProvider(name = "attrInfo")
  public Object[][] createData()
  {
@@ -74,9 +69,7 @@
    { att3, upd3, upd3 } };
  }
  /**
   * Create a AttrInfo and check the methods.
   */
  /** Create a AttrInfo and check the methods. */
  @Test(dataProvider = "attrInfo")
  public void attrInfo(ByteString att, CSN deleteTime, CSN updateTime) throws Exception
  {
@@ -85,10 +78,10 @@
    // Check
    attrInfo1.add(att, updateTime);
    Map<AttrValueHistorical,AttrValueHistorical> values1 = attrInfo1.getValuesHistorical();
    Set<AttrValueHistorical> values1 = attrInfo1.getValuesHistorical();
    assertEquals(values1.size(), 1);
    AttrValueHistorical valueInfo1 = new AttrValueHistorical(att, updateTime, null);
    assertTrue(values1.containsKey(valueInfo1));
    assertTrue(values1.contains(valueInfo1));
    // Check constructor with parameter
    AttrValueHistorical valueInfo2 = new AttrValueHistorical(att, updateTime, deleteTime);
@@ -101,14 +94,14 @@
    //  Check constructor with time parameter and not Value
    AttrHistoricalMultiple attrInfo3 = new AttrHistoricalMultiple(deleteTime, updateTime, null);
    attrInfo3.add(att, updateTime);
    Map<AttrValueHistorical,AttrValueHistorical> values3 = attrInfo3.getValuesHistorical();
    Set<AttrValueHistorical> values3 = attrInfo3.getValuesHistorical();
    assertEquals(values3.size(), 1);
    valueInfo1 = new AttrValueHistorical(att, updateTime, null);
    assertTrue(values3.containsKey(valueInfo1));
    assertTrue(values3.contains(valueInfo1));
    // Check duplicate
    AttrHistoricalMultiple attrInfo4 = attrInfo3.duplicate();
    Map<AttrValueHistorical,AttrValueHistorical> values4 = attrInfo4.getValuesHistorical();
    Set<AttrValueHistorical> values4 = attrInfo4.getValuesHistorical();
    assertEquals(attrInfo4.getDeleteTime().compareTo(attrInfo3.getDeleteTime()), 0);
    assertEquals(values4.size(), values3.size());
@@ -122,7 +115,7 @@
    assertEquals(attrInfo3.getValuesHistorical().size(), 1);
    // Check
    attrInfo2.delete(updateTime) ;
    attrInfo2.delete(updateTime);
    assertEquals(attrInfo2.getValuesHistorical().size(), 0);
  }
}