@Test
 public void testIsAccessAllowedNo() {
   byte[] mask =
       AccountAccessMask.createMask(
           Arrays.asList(
               new AccountAccessMask[] {
                 AccountAccessMask.ACCESS_ACCOUNT_BALANCE, AccountAccessMask.ACCESS_SKILL_QUEUE
               }));
   Assert.assertFalse(
       AccountAccessMask.isAccessAllowed(mask, AccountAccessMask.ACCESS_WALLET_JOURNAL));
 }
 @Test
 public void testIsAccessAllowedYes() {
   byte[] mask =
       AccountAccessMask.createMask(
           Arrays.asList(
               new AccountAccessMask[] {
                 AccountAccessMask.ACCESS_ACCOUNT_BALANCE, AccountAccessMask.ACCESS_SKILL_QUEUE
               }));
   Assert.assertTrue(
       AccountAccessMask.isAccessAllowed(mask, AccountAccessMask.ACCESS_SKILL_QUEUE));
 }
 @Test
 public void testIsValidMaskNo() {
   int max = 0;
   for (AccountAccessMask next : AccountAccessMask.values()) {
     max = Math.max(max, next.getMaskValue());
   }
   max = max + 1;
   byte[] testMask = new byte[((max + 1) / 8) + ((max + 1) % 8 > 0 ? 1 : 0)];
   int offset = max / 8;
   int bit = max % 8;
   testMask[offset] |= 1L << bit;
   Assert.assertFalse(AccountAccessMask.isValidMask(testMask));
 }
 @Test
 public void testCheckAccessNo() {
   byte[] mask =
       AccountAccessMask.createMask(
           Arrays.asList(new AccountAccessMask[] {AccountAccessMask.ACCESS_ASSETS}));
   Assert.assertFalse(AccountAccessMask.ACCESS_ACCOUNT_BALANCE.checkAccess(mask));
 }
 @Test
 public void testSetMask() {
   byte[] value = AccountAccessMask.setMask(new byte[0], AccountAccessMask.ACCESS_SKILL_QUEUE);
   Assert.assertTrue(AccountAccessMask.isValidMask(value));
   Assert.assertTrue(
       AccountAccessMask.isAccessAllowed(value, AccountAccessMask.ACCESS_SKILL_QUEUE));
   int val = AccountAccessMask.ACCESS_SKILL_QUEUE.getMaskValue();
   int offset = val / 8;
   int bit = val % 8;
   for (int i = 0; i < value.length; i++) {
     if (i == offset) {
       Assert.assertEquals(1L << bit, value[i]);
     } else {
       Assert.assertEquals(0, value[i]);
     }
   }
 }
 @Test
 public void testUnsetMask() {
   byte[] value = AccountAccessMask.setMask(new byte[0], AccountAccessMask.ACCESS_SKILL_QUEUE);
   value = AccountAccessMask.setMask(value, AccountAccessMask.ACCESS_ACCOUNT_BALANCE);
   Assert.assertTrue(AccountAccessMask.isValidMask(value));
   for (AccountAccessMask next : AccountAccessMask.values()) {
     switch (next) {
       case ACCESS_SKILL_QUEUE:
       case ACCESS_ACCOUNT_BALANCE:
         Assert.assertTrue(AccountAccessMask.isAccessAllowed(value, next));
         break;
       default:
         Assert.assertFalse(AccountAccessMask.isAccessAllowed(value, next));
     }
   }
 }
@Entity
@Table(name = "evekit_data_character_sheet_clone")
@NamedQueries({
  @NamedQuery(
      name = "CharacterSheetClone.get",
      query =
          "SELECT c FROM CharacterSheetClone c where c.owner = :owner and c.lifeStart <= :point and c.lifeEnd > :point"),
})
// 2 hour cache time - API caches for 1 hour
public class CharacterSheetClone extends CachedData {
  protected static final Logger log = Logger.getLogger(CharacterSheetClone.class.getName());
  private static final byte[] MASK =
      AccountAccessMask.createMask(AccountAccessMask.ACCESS_CHARACTER_SHEET);
  // Stores just the cloneJumpDate part of the character sheet since this may change
  // frequently and we want to avoid having to evolve the entire character sheet.
  private long cloneJumpDate = -1;

  @SuppressWarnings("unused")
  private CharacterSheetClone() {}

  public CharacterSheetClone(long cloneJumpDate) {
    this.cloneJumpDate = cloneJumpDate;
  }

  /** {@inheritDoc} */
  @Override
  public boolean equivalent(CachedData sup) {
    if (!(sup instanceof CharacterSheetClone)) return false;
    CharacterSheetClone other = (CharacterSheetClone) sup;
    return cloneJumpDate == other.cloneJumpDate;
  }

  /** {@inheritDoc} */
  @Override
  public byte[] getMask() {
    return MASK;
  }

  public long getCloneJumpDate() {
    return cloneJumpDate;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime * result + (int) (cloneJumpDate ^ (cloneJumpDate >>> 32));
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!super.equals(obj)) return false;
    if (getClass() != obj.getClass()) return false;
    CharacterSheetClone other = (CharacterSheetClone) obj;
    if (cloneJumpDate != other.cloneJumpDate) return false;
    return true;
  }

  @Override
  public String toString() {
    return "CharacterSheetClone [cloneJumpDate="
        + cloneJumpDate
        + ", owner="
        + owner
        + ", lifeStart="
        + lifeStart
        + ", lifeEnd="
        + lifeEnd
        + "]";
  }

  public static CharacterSheetClone get(final SynchronizedEveAccount owner, final long time) {
    try {
      return EveKitUserAccountProvider.getFactory()
          .runTransaction(
              new RunInTransaction<CharacterSheetClone>() {
                @Override
                public CharacterSheetClone run() throws Exception {
                  TypedQuery<CharacterSheetClone> getter =
                      EveKitUserAccountProvider.getFactory()
                          .getEntityManager()
                          .createNamedQuery("CharacterSheetClone.get", CharacterSheetClone.class);
                  getter.setParameter("owner", owner);
                  getter.setParameter("point", time);
                  try {
                    return getter.getSingleResult();
                  } catch (NoResultException e) {
                    return null;
                  }
                }
              });
    } catch (Exception e) {
      log.log(Level.SEVERE, "query error", e);
    }
    return null;
  }

  public static List<CharacterSheetClone> accessQuery(
      final SynchronizedEveAccount owner,
      final long contid,
      final int maxresults,
      final boolean reverse,
      final AttributeSelector at,
      final AttributeSelector cloneJumpDate) {
    try {
      return EveKitUserAccountProvider.getFactory()
          .runTransaction(
              new RunInTransaction<List<CharacterSheetClone>>() {
                @Override
                public List<CharacterSheetClone> run() throws Exception {
                  StringBuilder qs = new StringBuilder();
                  qs.append("SELECT c FROM CharacterSheetClone c WHERE ");
                  // Constrain to specified owner
                  qs.append("c.owner = :owner");
                  // Constrain lifeline
                  AttributeSelector.addLifelineSelector(qs, "c", at);
                  // Constrain attributes
                  AttributeSelector.addLongSelector(qs, "c", "cloneJumpDate", cloneJumpDate);
                  // Set CID constraint and ordering
                  if (reverse) {
                    qs.append(" and c.cid < ").append(contid);
                    qs.append(" order by cid desc");
                  } else {
                    qs.append(" and c.cid > ").append(contid);
                    qs.append(" order by cid asc");
                  }
                  // Return result
                  TypedQuery<CharacterSheetClone> query =
                      EveKitUserAccountProvider.getFactory()
                          .getEntityManager()
                          .createQuery(qs.toString(), CharacterSheetClone.class);
                  query.setParameter("owner", owner);
                  query.setMaxResults(maxresults);
                  return query.getResultList();
                }
              });
    } catch (Exception e) {
      log.log(Level.SEVERE, "query error", e);
    }
    return Collections.emptyList();
  }
}