package org.marketcetera.event.util; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import org.marketcetera.event.MarketstatEvent; import org.marketcetera.event.OptionMarketstatEvent; import org.marketcetera.event.impl.MarketstatEventBuilder; import org.marketcetera.trade.Instrument; import org.marketcetera.util.misc.ClassVersion; /* $License$ */ /** * Provides a thread-safe cache of {@link MarketstatEvent market statistics} * for a given {@link Instrument}. * * <p>The cache retains the most recent non-null attributes available. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: MarketstatEventCache.java 16864 2014-03-20 19:39:48Z colin $ * @since 2.0.0 */ @ThreadSafe @ClassVersion("$Id: MarketstatEventCache.java 16864 2014-03-20 19:39:48Z colin $") public class MarketstatEventCache { /** * Create a new MarketstatEventCache instance. * * <p>This class will organize {@link MarketstatEvent events} for * a given {@link Instrument}, guaranteeing a view with the composite * of all the received non-null attributes with a bias towards the most * recently received value. The timestamp, id, and source on the returned event * will reflect the most recently received update. * * @param inInstrument an <code>Instrument</code> value */ public MarketstatEventCache(Instrument inInstrument) { if(inInstrument == null) { throw new NullPointerException(); } builder = MarketstatEventBuilder.marketstat(inInstrument); instrument = inInstrument; } /** * Adds the given {@link MarketstatEvent event} to the cache. * * <p>Any non-null attributes on the given event will replace the * cached attribute. * * @param inEvent a <code>MarketstatEvent</code> value * @return a <code>MarketstatEvent</code> value * @throws IllegalArgumentException if the given event <code>Instrument</code> does not * match the <code>Instrument</code> for which this cache was created */ public synchronized MarketstatEvent cache(MarketstatEvent inEvent) { if(!inEvent.getInstrument().equals(instrument)) { throw new IllegalArgumentException(); } receivedData = true; // these values should always be transferred builder.withMessageId(inEvent.getMessageId()) .withTimestamp(inEvent.getTimestamp()) .withSource(inEvent.getSource()) .withEventType(inEvent.getEventType()); // these values should be transferred only if non-null if(inEvent.getClose() != null) { builder.withClosePrice(inEvent.getClose()); } if(inEvent.getCloseDate() != null) { builder.withCloseDate(inEvent.getCloseDate()); } if(inEvent.getCloseExchange() != null) { builder.withCloseExchange(inEvent.getCloseExchange()); } if(inEvent.getHigh() != null) { builder.withHighPrice(inEvent.getHigh()); } if(inEvent.getHighExchange() != null) { builder.withHighExchange(inEvent.getHighExchange()); } if(inEvent.getLow() != null) { builder.withLowPrice(inEvent.getLow()); } if(inEvent.getLowExchange() != null) { builder.withLowExchange(inEvent.getLowExchange()); } if(inEvent.getOpen() != null) { builder.withOpenPrice(inEvent.getOpen()); } if(inEvent.getOpenExchange() != null) { builder.withOpenExchange(inEvent.getOpenExchange()); } if(inEvent.getPreviousClose() != null) { builder.withPreviousClosePrice(inEvent.getPreviousClose()); } if(inEvent.getPreviousCloseDate() != null) { builder.withPreviousCloseDate(inEvent.getPreviousCloseDate()); } if(inEvent.getTradeHighTime() != null) { builder.withTradeHighTime(inEvent.getTradeHighTime()); } if(inEvent.getTradeLowTime() != null) { builder.withTradeLowTime(inEvent.getTradeLowTime()); } if(inEvent.getVolume() != null) { builder.withVolume(inEvent.getVolume()); } if(inEvent.getValue() != null) { builder.withValue(inEvent.getValue()); } if(inEvent instanceof OptionMarketstatEvent) { OptionMarketstatEvent optionEvent = (OptionMarketstatEvent)inEvent; builder.hasDeliverable(optionEvent.hasDeliverable()); if(optionEvent.getExpirationType() != null) { builder.withExpirationType(optionEvent.getExpirationType()); } if(optionEvent.getMultiplier() != null) { builder.withMultiplier(optionEvent.getMultiplier()); } if(optionEvent.getProviderSymbol() != null) { builder.withProviderSymbol(optionEvent.getProviderSymbol()); } if(optionEvent.getUnderlyingInstrument() != null) { builder.withUnderlyingInstrument(optionEvent.getUnderlyingInstrument()); } if(optionEvent.getVolumeChange() != null) { builder.withVolumeChange(optionEvent.getVolumeChange()); } if(optionEvent.getInterestChange() != null) { builder.withInterestChange(optionEvent.getInterestChange()); } } return get(); } /** * Returns a view that reflects the most recent attributes available. * * @return a <code>MarketstatEvent</code> value or <code>null</code> if no data has * been added to the cache */ public synchronized MarketstatEvent get() { if(!receivedData) { return null; } return builder.create(); } /** * the event builder used to cache the values */ @GuardedBy("this") private final MarketstatEventBuilder builder; /** * the instrument for this cache */ private final Instrument instrument; /** * indicates if the cache has received any data so far */ private boolean receivedData = false; }