package com.jbidwatcher.auction; /* * Copyright (c) 2000-2007, CyberFOX Software, Inc. All Rights Reserved. * * Developed by mrs (Morgan Schweers) */ import com.jbidwatcher.util.Currency; import com.jbidwatcher.util.db.ActiveRecord; import com.jbidwatcher.util.db.Table; import java.awt.Color; import java.util.*; /** * MultiSnipe class */ public class MultiSnipe extends ActiveRecord { private static EntryCorralTemplate corral = null; private static Map<Integer, MultiSnipe> singleSource = new HashMap<Integer, MultiSnipe>(); private static final int HEX_BASE = 16; private Color mBackground; public synchronized int activeEntries() { return getAuctionEntriesInThisGroup(corral).size(); } public static void setCorral(EntryCorralTemplate corral) { MultiSnipe.corral = corral; } private void setValues(Color groupColor, Currency snipeValue, long id, boolean subtractShipping) { mBackground = groupColor; setString("color", makeRGB(groupColor)); setMonetary("default_bid", snipeValue); setBoolean("subtract_shipping", subtractShipping); // Basically, the identifier is a long value based on // the time at which it's created. setString("identifier", Long.toString(id)); String myId = saveDB(); if(myId != null) singleSource.put(getId(), this); } /** @noinspection NonConstantStringShouldBeStringBuffer * @param groupColor - The color for the group. * @return - A string consisting of the hex equivalent for the color provided. */ // Construct a standard HTML 'bgcolor="#ffffff"' style color string. public static String makeRGB(Color groupColor) { String red = Integer.toString(groupColor.getRed(), HEX_BASE); if (red.length() == 1) red = '0' + red; String green = Integer.toString(groupColor.getGreen(), HEX_BASE); if (green.length() == 1) green = '0' + green; String blue = Integer.toString(groupColor.getBlue(), HEX_BASE); if (blue.length() == 1) blue = '0' + blue; return red + green + blue; } public static Color reverseColor(String colorText) { int red = Integer.parseInt(colorText.substring(0, 2), HEX_BASE); int green = Integer.parseInt(colorText.substring(2, 4), HEX_BASE); int blue = Integer.parseInt(colorText.substring(4, 6), HEX_BASE); return new Color(red, green, blue); } public MultiSnipe() { // This exists for construction via ActiveRecord loading... super(); } public MultiSnipe(String groupColor, Currency snipeValue, long id, boolean subtractShipping) { Color rgb = reverseColor(groupColor); setString("color", groupColor); setValues(rgb, snipeValue, id, subtractShipping); } public MultiSnipe(Color groupColor, Currency snipeValue, boolean subtractShipping) { setValues(groupColor, snipeValue, System.currentTimeMillis(), subtractShipping); } public Color getColor() { if(mBackground == null) { mBackground = reverseColor(getColorString()); } return mBackground; } public String getColorString() { return getString("color"); } public Currency getSnipeValue(Currency shipping) { if (shipping != null && !shipping.isNull() && getBoolean("subtract_shipping")) { try { return getMonetary("default_bid").subtract(shipping); } catch (Currency.CurrencyTypeException e) { // It's not relevant (although odd), we fall through to the return. } } return getMonetary("default_bid"); } public long getIdentifier() { return Long.parseLong(getString("identifier", "0")); } public synchronized void add(String identifier) { AuctionEntry ae = (AuctionEntry)corral.takeForWrite(identifier); try { ae.set("multisnipe_id", get("id")); if(!ae.isSniped()) { ae.prepareSnipe(getSnipeValue(ae.getShippingWithInsurance()), 1); } else { ae.saveDB(); } } finally { corral.release(identifier); } } public synchronized void remove(String identifier) { ActiveRecord ae = corral.takeForWrite(identifier); try { ae.set("multisnipe_id", null); ae.saveDB(); } finally { corral.release(identifier); } } /** * Right now it doesn't use the passed in parameter. I'm not sure * what it would do with it, but it seems right to pass it in. * * param ae - The auction that was won. */ public synchronized void setWonAuction(/*Snipeable ae*/) { List<? extends Snipeable> oldEntries = getAuctionEntriesInThisGroup(corral); for (Snipeable aeFromList : oldEntries) { aeFromList.cancelSnipe(false); } } public synchronized boolean anyEarlier(Snipeable inEntry) { String inIdentifier = inEntry.getIdentifier(); for (Snipeable ae : getAuctionEntriesInThisGroup(corral)) { // If any auction entry in the list ends BEFORE the one we're // checking, then we really don't want to do anything until // it's no longer in the list. if (ae.getEndDate().before(inEntry.getEndDate()) && !inIdentifier.equals(ae.getIdentifier())) return true; } return false; } /** * Determine if the two items listed constitute a safe multisnipe; i.e. * it's unlikely that the two will conflict. * * @param ae1 - The first snipeable item to check. * @param ae2 - The second snipeable item to check. * @return - true if the two auctions are far enough apart, false if it * could be dangerous to include both items in the same multisnipe group. */ public static boolean isSafeMultiSnipe(Snipeable ae1, Snipeable ae2) { long end1 = ae1.getEndDate().getTime(); long end2 = ae2.getEndDate().getTime(); long snipe1 = end1 - ae1.getSnipeTime(); long snipe2 = end2 - ae2.getSnipeTime(); /* * If they end at the same time, or A1 ends first, but within * {snipetime} seconds of A2, or A2 ends first, but within * {snipetime} seconds of A1, then it is NOT safe. */ return !((end1 == end2) || ((end1 < end2) && (end1 >= snipe2)) || ((end2 < end1) && (end2 >= snipe1))); } public synchronized boolean isSafeToAdd(Snipeable ae) { for (Snipeable fromList : getAuctionEntriesInThisGroup(corral)) { // It's always safe to 'add' an entry that already exists, // it'll just be reloaded. if (!fromList.getIdentifier().equals(ae.getIdentifier())) { if (!isSafeMultiSnipe(fromList, ae)) return false; } } return true; } private List<? extends Snipeable> getAuctionEntriesInThisGroup(EntryCorralTemplate corral) { return ((EntryCorral)corral).getMultisnipedByGroup(getString("id")); } public boolean subtractShipping() { return getBoolean("subtract_shipping"); } /*************************/ /* Database access stuff */ /*************************/ private static Table sDB = null; protected static String getTableName() { return "multisnipes"; } protected Table getDatabase() { if (sDB == null) { sDB = openDB(getTableName()); } return sDB; } private static MultiSnipe cacheResult(MultiSnipe rval) { if(rval != null) { if(singleSource.get(rval.getId()) != null) { rval = singleSource.get(rval.getId()); } else { singleSource.put(rval.getId(), rval); } } return rval; } public static MultiSnipe findFirstBy(String key, String value) { MultiSnipe rval = (MultiSnipe) ActiveRecord.findFirstBy(MultiSnipe.class, key, value); return cacheResult(rval); } public static MultiSnipe find(Integer id) { MultiSnipe rval = singleSource.get(id); if(rval == null) { rval = (MultiSnipe) ActiveRecord.findFirstBy(MultiSnipe.class, "id", Integer.toString(id)); singleSource.put(id, rval); } return rval; } public static boolean deleteAll(List<MultiSnipe> toDelete) { if(toDelete.isEmpty()) return true; String multisnipes = makeCommaList(toDelete); for(MultiSnipe ms : toDelete) { singleSource.remove(ms.getId()); } return toDelete.get(0).getDatabase().deleteBy("id IN (" + multisnipes + ")"); } }