/**
* Copyright (c) 2005-2017, KoLmafia development team
* http://kolmafia.sourceforge.net/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* [1] Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* [2] Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* [3] Neither the name "KoLmafia" nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package net.sourceforge.kolmafia.textui.parsetree;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.CoinmasterRegistry;
import net.sourceforge.kolmafia.EdServantData;
import net.sourceforge.kolmafia.KoLAdventure;
import net.sourceforge.kolmafia.MonsterData;
import net.sourceforge.kolmafia.PastaThrallData;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.VYKEACompanionData;
import net.sourceforge.kolmafia.persistence.AdventureDatabase;
import net.sourceforge.kolmafia.persistence.BountyDatabase;
import net.sourceforge.kolmafia.persistence.EffectDatabase;
import net.sourceforge.kolmafia.persistence.FamiliarDatabase;
import net.sourceforge.kolmafia.persistence.ItemDatabase;
import net.sourceforge.kolmafia.persistence.MonsterDatabase;
import net.sourceforge.kolmafia.persistence.SkillDatabase;
import net.sourceforge.kolmafia.request.EquipmentRequest;
import net.sourceforge.kolmafia.textui.DataTypes;
import net.sourceforge.kolmafia.textui.Interpreter;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class Type
extends Symbol
{
public boolean primitive;
private final int type;
private Value allValues = null;
public Type( final String name, final int type )
{
super( name );
this.primitive = true;
this.type = type;
}
public int getType()
{
return this.type;
}
public Type getBaseType()
{
return this;
}
public boolean isPrimitive()
{
return this.primitive;
}
public boolean equals( final Type type )
{
return this.type == type.type;
}
public boolean equals( final int type )
{
return this.type == type;
}
@Override
public String toString()
{
return this.name;
}
public Type simpleType()
{
return this;
}
public Type asProxy()
{
if ( this == DataTypes.CLASS_TYPE )
{
return ProxyRecordValue.ClassProxy._type;
}
if ( this == DataTypes.ITEM_TYPE )
{
return ProxyRecordValue.ItemProxy._type;
}
if ( this == DataTypes.FAMILIAR_TYPE )
{
return ProxyRecordValue.FamiliarProxy._type;
}
if ( this == DataTypes.SKILL_TYPE )
{
return ProxyRecordValue.SkillProxy._type;
}
if ( this == DataTypes.EFFECT_TYPE )
{
return ProxyRecordValue.EffectProxy._type;
}
if ( this == DataTypes.LOCATION_TYPE )
{
return ProxyRecordValue.LocationProxy._type;
}
if ( this == DataTypes.MONSTER_TYPE )
{
return ProxyRecordValue.MonsterProxy._type;
}
if ( this == DataTypes.COINMASTER_TYPE )
{
return ProxyRecordValue.CoinmasterProxy._type;
}
if ( this == DataTypes.BOUNTY_TYPE )
{
return ProxyRecordValue.BountyProxy._type;
}
if ( this == DataTypes.THRALL_TYPE )
{
return ProxyRecordValue.ThrallProxy._type;
}
if ( this == DataTypes.SERVANT_TYPE )
{
return ProxyRecordValue.ServantProxy._type;
}
if ( this == DataTypes.VYKEA_TYPE )
{
return ProxyRecordValue.VykeaProxy._type;
}
if ( this == DataTypes.ELEMENT_TYPE )
{
return ProxyRecordValue.ElementProxy._type;
}
if ( this == DataTypes.PHYLUM_TYPE )
{
return ProxyRecordValue.PhylumProxy._type;
}
return this;
}
public Value initialValue()
{
switch ( this.type )
{
case DataTypes.TYPE_VOID:
return DataTypes.VOID_VALUE;
case DataTypes.TYPE_BOOLEAN:
return DataTypes.BOOLEAN_INIT;
case DataTypes.TYPE_INT:
return DataTypes.INT_INIT;
case DataTypes.TYPE_FLOAT:
return DataTypes.FLOAT_INIT;
case DataTypes.TYPE_STRING:
return DataTypes.STRING_INIT;
case DataTypes.TYPE_BUFFER:
return new Value( DataTypes.BUFFER_TYPE, "", new StringBuffer() );
case DataTypes.TYPE_MATCHER:
return new Value( DataTypes.MATCHER_TYPE, "", Pattern.compile( "" ).matcher( "" ) );
case DataTypes.TYPE_ITEM:
return DataTypes.ITEM_INIT;
case DataTypes.TYPE_LOCATION:
return DataTypes.LOCATION_INIT;
case DataTypes.TYPE_CLASS:
return DataTypes.CLASS_INIT;
case DataTypes.TYPE_STAT:
return DataTypes.STAT_INIT;
case DataTypes.TYPE_SKILL:
return DataTypes.SKILL_INIT;
case DataTypes.TYPE_EFFECT:
return DataTypes.EFFECT_INIT;
case DataTypes.TYPE_FAMILIAR:
return DataTypes.FAMILIAR_INIT;
case DataTypes.TYPE_SLOT:
return DataTypes.SLOT_INIT;
case DataTypes.TYPE_MONSTER:
return DataTypes.MONSTER_INIT;
case DataTypes.TYPE_ELEMENT:
return DataTypes.ELEMENT_INIT;
case DataTypes.TYPE_COINMASTER:
return DataTypes.COINMASTER_INIT;
case DataTypes.TYPE_PHYLUM:
return DataTypes.PHYLUM_INIT;
case DataTypes.TYPE_BOUNTY:
return DataTypes.BOUNTY_INIT;
case DataTypes.TYPE_THRALL:
return DataTypes.THRALL_INIT;
case DataTypes.TYPE_SERVANT:
return DataTypes.SERVANT_INIT;
case DataTypes.TYPE_VYKEA:
return DataTypes.VYKEA_INIT;
}
return null;
}
public Value parseValue( final String name, final boolean returnDefault )
{
switch ( this.type )
{
case DataTypes.TYPE_BOOLEAN:
return DataTypes.parseBooleanValue( name, returnDefault );
case DataTypes.TYPE_INT:
return DataTypes.parseIntValue( name, returnDefault );
case DataTypes.TYPE_FLOAT:
return DataTypes.parseFloatValue( name, returnDefault );
case DataTypes.TYPE_STRING:
return DataTypes.parseStringValue( name );
case DataTypes.TYPE_ITEM:
return DataTypes.parseItemValue( name, returnDefault );
case DataTypes.TYPE_LOCATION:
return DataTypes.parseLocationValue( name, returnDefault );
case DataTypes.TYPE_CLASS:
return DataTypes.parseClassValue( name, returnDefault );
case DataTypes.TYPE_STAT:
return DataTypes.parseStatValue( name, returnDefault );
case DataTypes.TYPE_SKILL:
return DataTypes.parseSkillValue( name, returnDefault );
case DataTypes.TYPE_EFFECT:
return DataTypes.parseEffectValue( name, returnDefault );
case DataTypes.TYPE_FAMILIAR:
return DataTypes.parseFamiliarValue( name, returnDefault );
case DataTypes.TYPE_SLOT:
return DataTypes.parseSlotValue( name, returnDefault );
case DataTypes.TYPE_MONSTER:
return DataTypes.parseMonsterValue( name, returnDefault );
case DataTypes.TYPE_ELEMENT:
return DataTypes.parseElementValue( name, returnDefault );
case DataTypes.TYPE_COINMASTER:
return DataTypes.parseCoinmasterValue( name, returnDefault );
case DataTypes.TYPE_PHYLUM:
return DataTypes.parsePhylumValue( name, returnDefault );
case DataTypes.TYPE_BOUNTY:
return DataTypes.parseBountyValue( name, returnDefault );
case DataTypes.TYPE_THRALL:
return DataTypes.parseThrallValue( name, returnDefault );
case DataTypes.TYPE_SERVANT:
return DataTypes.parseServantValue( name, returnDefault );
case DataTypes.TYPE_VYKEA:
return DataTypes.parseVykeaValue( name, returnDefault );
}
return null;
}
public Value makeValue( final Integer idval, final boolean returnDefault )
{
int id = idval.intValue();
switch ( this.type )
{
case DataTypes.TYPE_BOOLEAN:
return DataTypes.makeBooleanValue( id );
case DataTypes.TYPE_INT:
return DataTypes.makeIntValue( id );
case DataTypes.TYPE_FLOAT:
return DataTypes.makeFloatValue( id );
case DataTypes.TYPE_STRING:
return new Value( String.valueOf( id ) );
case DataTypes.TYPE_ITEM:
return DataTypes.makeItemValue( id, returnDefault );
case DataTypes.TYPE_SKILL:
return DataTypes.makeSkillValue( id, returnDefault );
case DataTypes.TYPE_EFFECT:
return DataTypes.makeEffectValue( id, returnDefault );
case DataTypes.TYPE_FAMILIAR:
return DataTypes.makeFamiliarValue( id, returnDefault );
case DataTypes.TYPE_MONSTER:
return DataTypes.makeMonsterValue( id, returnDefault );
case DataTypes.TYPE_THRALL:
return DataTypes.makeThrallValue( id, returnDefault );
case DataTypes.TYPE_SERVANT:
return DataTypes.makeServantValue( id, returnDefault );
// The following don't have an integer -> object mapping
case DataTypes.TYPE_LOCATION:
return DataTypes.LOCATION_INIT;
case DataTypes.TYPE_CLASS:
return DataTypes.CLASS_INIT;
case DataTypes.TYPE_STAT:
return DataTypes.STAT_INIT;
case DataTypes.TYPE_SLOT:
return DataTypes.SLOT_INIT;
case DataTypes.TYPE_ELEMENT:
return DataTypes.ELEMENT_INIT;
case DataTypes.TYPE_COINMASTER:
return DataTypes.COINMASTER_INIT;
case DataTypes.TYPE_PHYLUM:
return DataTypes.PHYLUM_INIT;
case DataTypes.TYPE_BOUNTY:
return DataTypes.SERVANT_INIT;
}
return null;
}
public List<String> getAmbiguousNames( String s1, Value value, boolean quote )
{
switch ( this.type )
{
case DataTypes.TYPE_ITEM:
case DataTypes.TYPE_EFFECT:
{
String s2 = value.toString();
if ( s1.equalsIgnoreCase( s2 ) )
{
return null;
}
if ( s1.startsWith( "[" ) )
{
int bracket = s1.indexOf( "]" );
if ( bracket > 0 && StringUtilities.isNumeric( s1.substring( 1, bracket ) ) )
{
return null;
}
}
if ( StringUtilities.isNumeric( s1 ) )
{
// A number will have been unambiguously
// interpreted as an item or effect id
return null;
}
ArrayList<String> names = new ArrayList<String>();
int currentId = (int)value.contentLong;
String name =
this.type == DataTypes.TYPE_ITEM ?
ItemDatabase.getItemName( currentId ) :
EffectDatabase.getEffectName( currentId );
int[] ids =
this.type == DataTypes.TYPE_ITEM ?
ItemDatabase.getItemIds( name, 1, false ) :
EffectDatabase.getEffectIds( name, false );
for ( int id : ids )
{
String s3 =
quote ?
( "\"[" + String.valueOf( id ) + "]" + name + "\"" ) :
( "[" + String.valueOf( id ) + "]" + name );
names.add( s3 );
}
return names;
}
}
return null;
}
public void validateValue( final Interpreter interpreter, String s1, Value value )
{
List<String> names = this.getAmbiguousNames( s1, value, true );
if ( names != null && names.size() > 1 )
{
String s2 = value.toString();
Exception ex = interpreter.runtimeException2( "Multiple matches for \"" + s1 + "\"; using \"" + s2 + "\".",
"Clarify by using one of:" );
RequestLogger.printLine( ex.getMessage() );
for ( String str : names )
{
RequestLogger.printLine( str );
}
}
}
public Value coerceValue( final Object object, final boolean returnDefault )
{
if ( object instanceof String )
{
return this.parseValue( (String) object, returnDefault );
}
if ( object instanceof Integer )
{
int integer = ( (Integer) object ).intValue();
switch ( this.type )
{
case DataTypes.TYPE_BOOLEAN:
return DataTypes.makeBooleanValue( integer );
case DataTypes.TYPE_INT:
return DataTypes.makeIntValue( integer );
case DataTypes.TYPE_FLOAT:
return DataTypes.makeFloatValue( integer );
case DataTypes.TYPE_STRING:
return new Value( DataTypes.STRING_TYPE, String.valueOf( integer ) );
case DataTypes.TYPE_ITEM:
return DataTypes.makeItemValue( integer, returnDefault );
case DataTypes.TYPE_SKILL:
return DataTypes.makeSkillValue( integer, returnDefault );
case DataTypes.TYPE_EFFECT:
return DataTypes.makeEffectValue( integer, returnDefault );
case DataTypes.TYPE_FAMILIAR:
return DataTypes.makeFamiliarValue( integer, returnDefault );
case DataTypes.TYPE_MONSTER:
return DataTypes.makeMonsterValue( integer, returnDefault );
case DataTypes.TYPE_THRALL:
return DataTypes.makeThrallValue( integer, returnDefault );
case DataTypes.TYPE_SERVANT:
return DataTypes.makeServantValue( integer, returnDefault );
}
return null;
}
if ( object instanceof MonsterData )
{
MonsterData monster = (MonsterData) object;
switch ( this.type )
{
case DataTypes.TYPE_INT:
return DataTypes.makeIntValue( monster.getId() );
case DataTypes.TYPE_STRING:
return new Value( DataTypes.STRING_TYPE, monster.getName() );
case DataTypes.TYPE_MONSTER:
return DataTypes.makeMonsterValue( monster );
}
return null;
}
return null;
}
public Value allValues()
{
if ( this.allValues != null ) return this.allValues;
ArrayList<Value> list = new ArrayList<Value>();
switch ( this.type )
{
case DataTypes.TYPE_BOOLEAN:
this.addValues( list, DataTypes.BOOLEANS );
break;
case DataTypes.TYPE_ITEM:
int limit = ItemDatabase.maxItemId();
for ( int i = 1; i <= limit; ++i )
{
if ( i != 13 && ItemDatabase.getItemDataName( i ) != null )
{
list.add( DataTypes.makeItemValue( i, true ) );
}
}
break;
case DataTypes.TYPE_LOCATION:
this.addValues( list, AdventureDatabase.getAsLockableListModel() );
break;
case DataTypes.TYPE_CLASS:
this.addValues( list, DataTypes.CLASSES );
break;
case DataTypes.TYPE_STAT:
this.addValues( list, DataTypes.STAT_ARRAY, 0, 3 );
break;
case DataTypes.TYPE_SKILL:
this.addValues( list, SkillDatabase.entrySet() );
break;
case DataTypes.TYPE_EFFECT:
this.addValues( list, EffectDatabase.entrySet() );
break;
case DataTypes.TYPE_FAMILIAR:
this.addValues( list, FamiliarDatabase.entrySet() );
break;
case DataTypes.TYPE_SLOT:
this.addValues( list, EquipmentRequest.slotNames );
break;
case DataTypes.TYPE_MONSTER:
this.addValues( list, MonsterDatabase.entrySet() );
break;
case DataTypes.TYPE_ELEMENT:
this.addValues( list, MonsterDatabase.ELEMENT_ARRAY, 1, -1 );
break;
case DataTypes.TYPE_COINMASTER:
this.addValues( list, CoinmasterRegistry.MASTERS );
break;
case DataTypes.TYPE_PHYLUM:
this.addValues( list, MonsterDatabase.PHYLUM_ARRAY, 1, -1 );
break;
case DataTypes.TYPE_BOUNTY:
this.addValues( list, BountyDatabase.entrySet() );
break;
case DataTypes.TYPE_THRALL:
this.addValues( list, PastaThrallData.THRALL_ARRAY );
break;
case DataTypes.TYPE_SERVANT:
this.addValues( list, EdServantData.SERVANT_ARRAY );
break;
case DataTypes.TYPE_VYKEA:
this.addValues( list, VYKEACompanionData.VYKEA );
break;
default:
return null;
}
this.allValues = new PluralValue( this, list );
return this.allValues;
}
private void addValues( ArrayList<Value> results, String[] values )
{
this.addValues( results, values, 0, -1 );
}
private void addValues( ArrayList<Value> results, String[] values, int start, int stop )
{
if ( stop == -1 ) stop = values.length;
for ( int i = start; i < stop; ++i )
{
Value v = this.parseValue( values[ i ], false );
if ( v != null ) results.add( v );
}
}
private void addValues( ArrayList<Value> results, Collection values )
{
for ( Object o : values )
{
if ( o instanceof Map.Entry )
{ // Some of the database entrySet() methods return
// Integer:String mappings, others String:<something>.
// We prefer the former, but can handle either
Map.Entry e = (Map.Entry) o;
o = e.getKey();
}
if ( o instanceof KoLAdventure )
{ // KoLAdventure.toString() returns "zone: location",
// which isn't parseable as an ASH location.
o = ((KoLAdventure) o).getAdventureName();
}
Value v = this.coerceValue( o, false );
if ( v != null ) results.add( v );
}
}
public Value initialValueExpression()
{
return new TypeInitializer( this );
}
public boolean containsAggregate()
{
return false;
}
@Override
public Value execute( final Interpreter interpreter )
{
return null;
}
@Override
public void print( final PrintStream stream, final int indent )
{
Interpreter.indentLine( stream, indent );
stream.println( "<TYPE " + this.name + ">" );
}
}