package com.laytonsmith.core.natives.interfaces;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import java.util.Arrays;
import java.util.Set;
/**
* Things that implement this can be accessed like an array, with array_get, or [].
*/
@typeof("ArrayAccess")
public interface ArrayAccess extends Mixed, Sizable {
/**
* Return the mixed at this location. This should throw an exception if
* the index does not exist. This method will not be called if
* {@link #isAssociative()} returns false.
* @param index
* @param t
* @return
*/
public Construct get(String index, Target t) throws ConfigRuntimeException;
/**
* Returns the mixed at this location. This should throw an exception if
* the index does not exist. This method will not be called if
* {@link #isAssociative()} returns true.
* @param index
* @param t
* @return
* @throws ConfigRuntimeException
*/
public Construct get(int index, Target t) throws ConfigRuntimeException;
/**
* Returns the mixed at this location. This should throw an exception if
* the index does not exist. This method may be called whether or not
* it isAssociative returns true.
* @param index
* @param t
* @return
* @throws ConfigRuntimeException
*/
public Construct get(Construct index, Target t) throws ConfigRuntimeException;
/**
* If {@link #isAssociative()} returns true, this should return a set of all
* keys. If {@link #isAssociative()} returns false, this method will not be
* called.
* @return
*/
public Set<Construct> keySet();
/**
* Return the size of the array
* @return
*/
@Override
public long size();
/**
* Unlike {@link #canBeAssociative()}, this is a runtime flag. If the underlying
* object is associative (that is, it is an unordered, non numeric key set), this should
* return true. If this is true, then {@link #get(java.lang.String, com.laytonsmith.core.constructs.Target)}
* will not be called, and {@link #get(int, com.laytonsmith.core.constructs.Target)} will be called
* instead. If this is false, the opposite will occur.
* @return
*/
public boolean isAssociative();
/**
* Just because it is accessible as an array doesn't mean it will be associative. For optimiziation purposes, it
* may be possible to check at compile time if the code is attempting to send a non-integral index,
* in which case we can throw a compile error. This is a compile time flag, and is not used during
* the runtime, just during compilation.
* @return
*/
public boolean canBeAssociative();
/**
* Returns a slice at the specified location. Should throw an exception if an element in
* the range doesn't exist.
* @param begin
* @param end
* @param t
* @return
*/
public Construct slice(int begin, int end, Target t);
/**
* This class contains iteration information for the ArrayAccess object
* as it is being iterated. This assumes that the object being iterated
* is not associative. Associative arrays have far simpler handling,
* and can therefore skip the handling needed for non-associative arrays.
*/
public static class ArrayAccessIterator {
private final ArrayAccess array;
private int current = 0;
private int[] blacklist = new int[]{-1};
private int blacklistSize = 0;
/**
* Creates a new ArrayAccessIterator. If the array is associative,
* a RuntimeException is thrown, since associative arrays have
* far simpler handling, and should not use this mechanism.
* @param array
*/
public ArrayAccessIterator(ArrayAccess array){
if(array.isAssociative()){
throw new RuntimeException();
}
this.array = array;
}
/**
* Returns the index of the currently iterated object.
* @return
*/
public int getCurrent(){
return current;
}
/**
* Decrements the current counter. This operation is used when the current
* item, or an item before the current item is removed.
*/
public void decrementCurrent(){
--current;
}
/**
* Increments the current counter. This operation is used when a new
* item is inserted before or at the current item. It is also used at the
* end of the loop.
*/
public void incrementCurrent(){
++current;
}
/**
* Increments all the values in the blacklist. This is used when a new
* item is inserted into the array, after the current index.
* @param from The value to search from. Any values after this are incremented,
* and values before it are not.
*/
public void incrementBlacklistAfter(int from){
for(int i = 0; i < blacklist.length; i++){
if(blacklist[i] > from){
blacklist[i]++;
}
}
}
/**
* Adds a value to the blacklist. This is used when a value is added after
* the current index. This value will not be iterated in the future.
* @param index The index to add to the blacklist.
*/
public void addToBlacklist(int index){
if(blacklistSize == blacklist.length){
int[] bl = new int[blacklist.length * 2];
Arrays.fill(bl, -1);
System.arraycopy(blacklist, 0, bl, 0, blacklist.length);
blacklist = bl;
}
blacklist[blacklistSize] = index;
blacklistSize++;
}
/**
* Checks through the blacklist, and returns true if this index
* is blacklisted. If so, this value should be skipped in the iteration.
* @param index The index to check.
* @return True if this value should be skipped.
*/
public boolean isBlacklisted(int index){
for(int v : blacklist){
if(v == index){
return true;
}
}
return false;
}
/**
* Gets the underlying ArrayAccess object.
* @return
*/
public ArrayAccess underlyingArray(){
return array;
}
}
@Override
public String docs();
@Override
public Version since();
}