/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Applied Energistics 2. If not, see . */ package appeng.me.cache; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import appeng.api.config.AccessRestriction; import appeng.api.config.Actionable; import appeng.api.networking.events.MENetworkStorageEvent; import appeng.api.networking.security.BaseActionSource; import appeng.api.storage.IMEInventoryHandler; import appeng.api.storage.IMEMonitor; import appeng.api.storage.IMEMonitorHandlerReceiver; import appeng.api.storage.StorageChannel; import appeng.api.storage.data.IAEStack; import appeng.api.storage.data.IItemList; import appeng.me.storage.ItemWatcher; public class NetworkMonitor> implements IMEMonitor { @Nonnull private static final Deque> GLOBAL_DEPTH = Lists.newLinkedList(); @Nonnull private final GridStorageCache myGridCache; @Nonnull private final StorageChannel myChannel; @Nonnull private final IItemList cachedList; @Nonnull private final Map, Object> listeners; private boolean sendEvent = false; private boolean hasChanged = false; @Nonnegative private int localDepthSemaphore = 0; public NetworkMonitor( final GridStorageCache cache, final StorageChannel chan ) { this.myGridCache = cache; this.myChannel = chan; this.cachedList = (IItemList) chan.createList(); this.listeners = new HashMap, Object>(); } @Override public void addListener( final IMEMonitorHandlerReceiver l, final Object verificationToken ) { this.listeners.put( l, verificationToken ); } @Override public boolean canAccept( final T input ) { return this.getHandler().canAccept( input ); } @Override public T extractItems( final T request, final Actionable mode, final BaseActionSource src ) { if( mode == Actionable.SIMULATE ) { return this.getHandler().extractItems( request, mode, src ); } localDepthSemaphore++; final T leftover = this.getHandler().extractItems( request, mode, src ); localDepthSemaphore--; if( localDepthSemaphore == 0 ) { this.monitorDifference( request.copy(), leftover, true, src ); } return leftover; } @Override public AccessRestriction getAccess() { return this.getHandler().getAccess(); } @Override public IItemList getAvailableItems( final IItemList out ) { return this.getHandler().getAvailableItems( out ); } @Override public StorageChannel getChannel() { return this.getHandler().getChannel(); } @Override public int getPriority() { return this.getHandler().getPriority(); } @Override public int getSlot() { return this.getHandler().getSlot(); } @Nonnull @Override public IItemList getStorageList() { if( this.hasChanged ) { this.hasChanged = false; this.cachedList.resetStatus(); return this.getAvailableItems( this.cachedList ); } return this.cachedList; } @Override public T injectItems( final T input, final Actionable mode, final BaseActionSource src ) { if( mode == Actionable.SIMULATE ) { return this.getHandler().injectItems( input, mode, src ); } localDepthSemaphore++; final T leftover = this.getHandler().injectItems( input, mode, src ); localDepthSemaphore--; if( localDepthSemaphore == 0 ) { this.monitorDifference( input.copy(), leftover, false, src ); } return leftover; } @Override public boolean isPrioritized( final T input ) { return this.getHandler().isPrioritized( input ); } @Override public void removeListener( final IMEMonitorHandlerReceiver l ) { this.listeners.remove( l ); } @Override public boolean validForPass( final int i ) { return this.getHandler().validForPass( i ); } @Nullable @SuppressWarnings( "unchecked" ) private IMEInventoryHandler getHandler() { switch( this.myChannel ) { case ITEMS: return (IMEInventoryHandler) this.myGridCache.getItemInventoryHandler(); case FLUIDS: return (IMEInventoryHandler) this.myGridCache.getFluidInventoryHandler(); default: } return null; } private Iterator, Object>> getListeners() { return this.listeners.entrySet().iterator(); } private T monitorDifference( final IAEStack original, final T leftOvers, final boolean extraction, final BaseActionSource src ) { final T diff = (T) original.copy(); if( extraction ) { diff.setStackSize( leftOvers == null ? 0 : -leftOvers.getStackSize() ); } else if( leftOvers != null ) { diff.decStackSize( leftOvers.getStackSize() ); } if( diff.getStackSize() != 0 ) { this.postChangesToListeners( ImmutableList.of( diff ), src ); } return leftOvers; } private void notifyListenersOfChange( final Iterable diff, final BaseActionSource src ) { this.hasChanged = true; final Iterator, Object>> i = this.getListeners(); while( i.hasNext() ) { final Entry, Object> o = i.next(); final IMEMonitorHandlerReceiver receiver = o.getKey(); if( receiver.isValid( o.getValue() ) ) { receiver.postChange( this, diff, src ); } else { i.remove(); } } } private void postChangesToListeners( final Iterable changes, final BaseActionSource src ) { this.postChange( true, changes, src ); } protected void postChange( final boolean add, final Iterable changes, final BaseActionSource src ) { if( localDepthSemaphore > 0 || GLOBAL_DEPTH.contains( this ) ) { return; } GLOBAL_DEPTH.push( this ); localDepthSemaphore++; this.sendEvent = true; this.notifyListenersOfChange( changes, src ); for( final T changedItem : changes ) { T difference = changedItem; if( !add && changedItem != null ) { difference = changedItem.copy(); difference.setStackSize( -changedItem.getStackSize() ); } if( this.myGridCache.getInterestManager().containsKey( changedItem ) ) { final Collection list = this.myGridCache.getInterestManager().get( changedItem ); if( !list.isEmpty() ) { IAEStack fullStack = this.getStorageList().findPrecise( changedItem ); if( fullStack == null ) { fullStack = changedItem.copy(); fullStack.setStackSize( 0 ); } this.myGridCache.getInterestManager().enableTransactions(); for( final ItemWatcher iw : list ) { iw.getHost().onStackChange( this.getStorageList(), fullStack, difference, src, this.getChannel() ); } this.myGridCache.getInterestManager().disableTransactions(); } } } final NetworkMonitor last = GLOBAL_DEPTH.pop(); localDepthSemaphore--; if( last != this ) { throw new IllegalStateException( "Invalid Access to Networked Storage API detected." ); } } void forceUpdate() { this.hasChanged = true; final Iterator, Object>> i = this.getListeners(); while( i.hasNext() ) { final Entry, Object> o = i.next(); final IMEMonitorHandlerReceiver receiver = o.getKey(); if( receiver.isValid( o.getValue() ) ) { receiver.onListUpdate(); } else { i.remove(); } } } void onTick() { if( this.sendEvent ) { this.sendEvent = false; this.myGridCache.getGrid().postEvent( new MENetworkStorageEvent( this, this.myChannel ) ); } } }