1515import appeng .api .parts .PartItemStack ;
1616import appeng .api .storage .IMEMonitor ;
1717import appeng .api .storage .data .IAEItemStack ;
18+ import appeng .api .storage .data .IItemList ;
1819import appeng .api .util .AECableType ;
1920import appeng .api .util .DimensionalCoord ;
2021import appeng .util .item .AEItemStack ;
4344import org .apache .commons .lang3 .StringUtils ;
4445
4546import java .util .ArrayList ;
47+ import java .util .Arrays ;
4648import java .util .List ;
49+ import java .util .Optional ;
50+ import java .util .function .Predicate ;
4751import java .util .regex .Pattern ;
52+ import java .util .stream .IntStream ;
4853
4954public class PartOreDictExporter extends PartECBase implements IGridTickable {
5055
5156 private String filter = "" ;
52- private ItemStack [] filteredItems = new ItemStack [0 ];
57+ /**
58+ * Used when there are ModID(@) and path (~) matchers. Tick-time matching.
59+ */
60+ private Predicate <ItemStack > filterPredicate = null ;
61+ /**
62+ * White list of itemstacks to extract. OreDict only mode.
63+ */
64+ private ItemStack [] oreDictFilteredItems ;
5365
5466 @ Override
5567 public float getCableConnectionLength (AECableType aeCableType ) {
@@ -62,27 +74,127 @@ public String getFilter() {
6274
6375 public void setFilter (String filter ) {
6476 this .filter = filter ;
65- updateFilteredItems ();
77+ updateFilter ();
6678 saveData ();
6779 }
6880
69- private void updateFilteredItems () {
81+ /**
82+ * Call when the filter string has changed to parse and recompile the filter.
83+ */
84+ private void updateFilter () {
7085 if (!this .filter .trim ().isEmpty ()) {
71- String filterRegexFragment = this .filter .replace ("*" , ".*" ).replace ("+" , ".+" );
72- String regexPattern = "^" + filterRegexFragment + "$" ;
73- Pattern pattern = Pattern .compile (regexPattern , Pattern .CASE_INSENSITIVE & Pattern .CANON_EQ );
7486 ArrayList <String > matchingNames = new ArrayList <>();
75- for (String oreName : OreDictionary .getOreNames ()) {
76- if (pattern .matcher (oreName ).matches ())
77- matchingNames .add (oreName );
87+ Predicate <ItemStack > matcher = null ;
88+
89+ String [] filters = this .filter .split ("[&|]" );
90+ String lastFilter = null ;
91+
92+ for (String filter : filters ) {
93+ filter = filter .trim ();
94+ boolean negated = filter .startsWith ("!" );
95+ if (negated )
96+ filter = filter .substring (1 );
97+
98+ Predicate <ItemStack > test = filterToItemStackPredicate (filter );
99+
100+ if (negated )
101+ test = test .negate ();
102+
103+ if (matcher == null ) {
104+ matcher = test ;
105+ lastFilter = filter ;
106+ } else {
107+ int endLast = this .filter .indexOf (lastFilter ) + lastFilter .length ();
108+ int startThis = this .filter .indexOf (filter );
109+ boolean or = this .filter .substring (endLast , startThis ).contains ("|" );
110+
111+ if (or ) {
112+ matcher = matcher .or (test );
113+ } else {
114+ matcher = matcher .and (test );
115+ }
116+ }
117+ }
118+
119+ if (matcher == null ) {
120+ filterPredicate = null ;
121+ oreDictFilteredItems = new ItemStack [0 ];
122+ return ;
123+ }
124+
125+ //Mod name and path evaluation can only be done during tick, can't precompile whitelist for this.
126+ if (!this .filter .contains ("@" ) && !this .filter .contains ("~" )) {
127+ //Precompiled whitelist of oredict itemstacks.
128+ this .oreDictFilteredItems = Arrays .stream (OreDictionary .getOreNames ())
129+ .flatMap (name -> OreDictionary .getOres (name ).stream ())
130+ .filter (matcher )
131+ .toArray (ItemStack []::new );
132+ this .filterPredicate = null ;
133+ } else {
134+ //Runtime evaluation of filter.
135+ filterPredicate = matcher ;
136+ this .oreDictFilteredItems = new ItemStack [0 ];
78137 }
138+ } else {
139+ this .filterPredicate = null ;
140+ this .oreDictFilteredItems = new ItemStack [0 ];
141+ }
142+ }
143+
144+ /**
145+ * Given a filter string, returns a predicate that matches a given ItemStack
146+ * @param filter Filter string.
147+ * @return Predicate for filter string.
148+ */
149+ private Predicate <ItemStack > filterToItemStackPredicate (String filter ) {
150+ if (filter .startsWith ("@" )) {
151+ final Predicate <String > test = filterToPredicate (filter .substring (1 ));
152+ return (is ) ->
153+ Optional .ofNullable (is .getItem ().getRegistryName ())
154+ .map (ResourceLocation ::getNamespace )
155+ .map (test ::test )
156+ .orElse (false );
157+ } else if (filter .startsWith ("~" )) {
158+ final Predicate <String > test = filterToPredicate (filter .substring (1 ));
159+ return (is ) ->
160+ Optional .ofNullable (is .getItem ().getRegistryName ())
161+ .map (ResourceLocation ::getPath )
162+ .map (test ::test )
163+ .orElse (false );
164+ } else {
165+ final Predicate <String > test = filterToPredicate (filter );
166+ return (is ) ->
167+ IntStream .of (OreDictionary .getOreIDs (is ))
168+ .mapToObj (OreDictionary ::getOreName )
169+ .anyMatch (test );
170+ }
171+ }
79172
80- this .filteredItems = matchingNames .stream ()
81- .map (OreDictionary ::getOres )
82- .flatMap (List ::stream )
83- .toArray (ItemStack []::new );
173+ /**
174+ * Given a filter string, returns a Predicate that matches a string.
175+ * @param filter Filter string
176+ * @return Predicate for filter string.
177+ */
178+ private Predicate <String > filterToPredicate (String filter ) {
179+ int numStars = StringUtils .countMatches (filter , '*' );
180+ if (numStars == filter .length ()) {
181+ return (str ) -> true ;
182+ } else if (filter .length () > 2 && filter .startsWith ("*" ) && filter .endsWith ("*" ) && numStars == 2 ) {
183+ final String pattern = filter .substring (1 , filter .length () - 1 );
184+ return (str ) -> str .contains (pattern );
185+ } else if (filter .length () >= 2 && filter .startsWith ("*" ) && numStars == 1 ) {
186+ final String pattern = filter .substring (1 );
187+ return (str ) -> str .endsWith (pattern );
188+ } else if (filter .length () >= 2 && filter .endsWith ("*" ) && numStars == 1 ) {
189+ final String pattern = filter .substring (0 , filter .length () - 1 );
190+ return (str ) -> str .startsWith (pattern );
191+ } else if (numStars == 0 ) {
192+ return (str ) -> str .equals (filter );
84193 } else {
85- this .filteredItems = new ItemStack [0 ];
194+ String filterRegexFragment = filter .replace ("*" , ".*" );
195+ String regexPattern = "^" + filterRegexFragment + "$" ;
196+ final Pattern pattern = Pattern .compile (regexPattern );
197+ return pattern .asPredicate ();
86198 }
87199 }
88200
@@ -187,22 +299,42 @@ public boolean doWork(int rate, int ticksSinceLastCall) {
187299 IMEMonitor <IAEItemStack > inv = storage .getInventory (StorageChannels .ITEM ());
188300 MachineSource src = new MachineSource (this );
189301
190- boolean exportedStuff = false ;
302+ if (this .filterPredicate != null ) {
303+ //Tick-time filter evaluation.
304+ IItemList <IAEItemStack > items = inv .getStorageList ();
305+ for (IAEItemStack stack : items ) {
306+ if (this .filterPredicate .test (stack .createItemStack ())) {
307+ IAEItemStack toExtract = stack .copy ();
308+ toExtract .setStackSize (amount );
191309
192- for (ItemStack is : this .filteredItems ) {
193- ItemStack toExtract = is .copy ();
194- toExtract .setCount (amount );
310+ IAEItemStack extracted = inv .extractItems (toExtract , Actionable .SIMULATE , src );
311+ if (extracted != null ) {
312+ IAEItemStack exported = exportStack (extracted .copy ());
313+ if (exported != null ) {
314+ inv .extractItems (exported , Actionable .MODULATE , src );
315+ return true ;
316+ }
317+ }
318+ }
319+ }
320+ return false ;
321+ } else {
322+ //Precompiled oredict whitelist
323+ for (ItemStack is : this .oreDictFilteredItems ) {
324+ ItemStack toExtract = is .copy ();
325+ toExtract .setCount (amount );
195326
196- IAEItemStack extracted = inv .extractItems (AEItemStack .fromItemStack (toExtract ), Actionable .SIMULATE , src );
197- if (extracted != null ) {
198- IAEItemStack exported = exportStack (extracted .copy ());
199- if (exported != null ) {
200- inv .extractItems (exported , Actionable .MODULATE , src );
201- return true ;
327+ IAEItemStack extracted = inv .extractItems (AEItemStack .fromItemStack (toExtract ), Actionable .SIMULATE , src );
328+ if (extracted != null ) {
329+ IAEItemStack exported = exportStack (extracted .copy ());
330+ if (exported != null ) {
331+ inv .extractItems (exported , Actionable .MODULATE , src );
332+ return true ;
333+ }
202334 }
203335 }
336+ return false ;
204337 }
205- return false ;
206338 }
207339
208340 public IAEItemStack exportStack (IAEItemStack stack0 ) {
@@ -387,7 +519,7 @@ public void readFromNBT(NBTTagCompound data) {
387519 super .readFromNBT (data );
388520 if (data .hasKey ("filter" )) {
389521 this .filter = data .getString ("filter" );
390- updateFilteredItems ();
522+ updateFilter ();
391523 }
392524 }
393525
0 commit comments