/** * 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.Comparator; import java.util.List; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.VYKEACompanionData; import net.sourceforge.kolmafia.textui.DataTypes; import net.sourceforge.kolmafia.textui.Interpreter; import net.sourceforge.kolmafia.textui.Parser; import net.sourceforge.kolmafia.utilities.StringUtilities; import org.json.JSONException; public class Value extends ParseTreeNode implements Comparable<Value> { public Type type; public long contentLong = 0; public String contentString = null; public Object content = null; public Value() { this.type = DataTypes.VOID_TYPE; } public Value( final long value ) { this.type = DataTypes.INT_TYPE; this.contentLong = value; } public Value( final boolean value ) { this.type = DataTypes.BOOLEAN_TYPE; this.contentLong = value ? 1 : 0; } public Value( final String value ) { this.type = DataTypes.STRING_TYPE; this.contentString = value == null ? "" : value; } public Value( final double value ) { this.type = DataTypes.FLOAT_TYPE; this.contentLong = Double.doubleToRawLongBits( value ); } public Value( final Type type ) { this.type = type; } public Value( final Type type, final String contentString ) { this.type = type; this.contentString = contentString; } public Value( final Type type, final long contentLong, final String contentString ) { this.type = type; this.contentLong = contentLong; this.contentString = contentString; } public Value( final Type type, final String contentString, final Object content ) { this.type = type; this.contentString = contentString; this.content = content; } public Value( final Type type, final long contentLong, final String contentString, final Object content ) { this.type = type; this.contentLong = contentLong; this.contentString = contentString; this.content = content; } public Value( final Value original ) { this.type = original.type; this.contentLong = original.contentLong; this.contentString = original.contentString; this.content = original.content; } public Value toFloatValue() { if ( this.type.equals( DataTypes.TYPE_FLOAT ) ) { return this; } return DataTypes.makeFloatValue( (double) this.contentLong ); } public Value toIntValue() { if ( this.type.equals( DataTypes.TYPE_INT ) ) { return this; } if ( this.type.equals( DataTypes.TYPE_BOOLEAN ) ) { return DataTypes.makeIntValue( this.contentLong != 0 ); } return DataTypes.makeIntValue( (long) this.floatValue() ); } public Value toBooleanValue() { if ( this.type.equals( DataTypes.TYPE_BOOLEAN ) ) { return this; } return DataTypes.makeBooleanValue( this.contentLong != 0 ); } public Type getType() { return this.type.getBaseType(); } @Override public String toString() { if ( this.content instanceof StringBuffer ) { return ( (StringBuffer) this.content ).toString(); } if ( this.type.equals( DataTypes.TYPE_VOID ) ) { return "void"; } if ( this.contentString != null ) { return this.contentString; } if ( this.type.equals( DataTypes.TYPE_BOOLEAN ) ) { return String.valueOf( this.contentLong != 0 ); } if ( this.type.equals( DataTypes.TYPE_FLOAT ) ) { return KoLConstants.NONSCIENTIFIC_FORMAT.format( this.floatValue() ); } return String.valueOf( this.contentLong ); } public String toQuotedString() { if ( this.contentString != null ) { return "\"" + this.contentString + "\""; } return this.toString(); } public Value toStringValue() { return new Value( this.toString() ); } public Object rawValue() { return this.content; } public long intValue() { if ( this.type.equals( DataTypes.TYPE_FLOAT ) ) { return (long) Double.longBitsToDouble( this.contentLong ); } return this.contentLong; } public double floatValue() { if ( !this.type.equals( DataTypes.TYPE_FLOAT ) ) { return (double) this.contentLong; } return Double.longBitsToDouble( this.contentLong ); } @Override public Value execute( final Interpreter interpreter ) { return this; } public Value asProxy() { if ( this.type == DataTypes.CLASS_TYPE ) { return new ProxyRecordValue.ClassProxy( this ); } if ( this.type == DataTypes.ITEM_TYPE ) { return new ProxyRecordValue.ItemProxy( this ); } if ( this.type == DataTypes.FAMILIAR_TYPE ) { return new ProxyRecordValue.FamiliarProxy( this ); } if ( this.type == DataTypes.SKILL_TYPE ) { return new ProxyRecordValue.SkillProxy( this ); } if ( this.type == DataTypes.EFFECT_TYPE ) { return new ProxyRecordValue.EffectProxy( this ); } if ( this.type == DataTypes.LOCATION_TYPE ) { if ( this.content == null ) { // All attribute lookups on $location[none] would generate NPEs, // so instead of adding a null check to each attribute, just // return a normal record with default values. return new RecordValue( ProxyRecordValue.LocationProxy._type ); } return new ProxyRecordValue.LocationProxy( this ); } if ( this.type == DataTypes.MONSTER_TYPE ) { if ( this.content == null ) { // Ditto return new RecordValue( ProxyRecordValue.MonsterProxy._type ); } return new ProxyRecordValue.MonsterProxy( this ); } if ( this.type == DataTypes.COINMASTER_TYPE ) { if ( this.content == null ) { // Ditto return new RecordValue( ProxyRecordValue.CoinmasterProxy._type ); } return new ProxyRecordValue.CoinmasterProxy( this ); } if ( this.type == DataTypes.BOUNTY_TYPE ) { return new ProxyRecordValue.BountyProxy( this ); } if ( this.type == DataTypes.THRALL_TYPE ) { return new ProxyRecordValue.ThrallProxy( this ); } if ( this.type == DataTypes.SERVANT_TYPE ) { return new ProxyRecordValue.ServantProxy( this ); } if ( this.type == DataTypes.VYKEA_TYPE ) { return new ProxyRecordValue.VykeaProxy( this ); } if ( this.type == DataTypes.ELEMENT_TYPE ) { return new ProxyRecordValue.ElementProxy( this ); } if ( this.type == DataTypes.PHYLUM_TYPE ) { return new ProxyRecordValue.PhylumProxy( this ); } return this; } /* null-safe version of the above */ public static Value asProxy( Value value ) { if ( value == null ) { return null; } return value.asProxy(); } public static final Comparator<Value> ignoreCaseComparator = new Comparator<Value>() { public int compare( Value v1, Value v2 ) { return v1.compareToIgnoreCase( v2 ); } }; public int compareTo( final Value o ) { return this.compareTo( o, false ); } public int compareToIgnoreCase( final Value o ) { return this.compareTo( o, true ); } private int compareTo( final Value o, final boolean ignoreCase ) { if ( !( o instanceof Value ) ) { throw new ClassCastException(); } Value it = (Value) o; if ( this.type == DataTypes.BOOLEAN_TYPE || this.type == DataTypes.INT_TYPE || this.type == DataTypes.ITEM_TYPE || this.type == DataTypes.EFFECT_TYPE || this.type == DataTypes.CLASS_TYPE || this.type == DataTypes.SKILL_TYPE || this.type == DataTypes.FAMILIAR_TYPE || this.type == DataTypes.SLOT_TYPE || this.type == DataTypes.THRALL_TYPE || this.type == DataTypes.SERVANT_TYPE ) { return this.contentLong < it.contentLong ? -1 : this.contentLong == it.contentLong ? 0 : 1; } if ( this.type == DataTypes.VYKEA_TYPE ) { // Let the underlying data type itself decide VYKEACompanionData v1 = (VYKEACompanionData)( this.content ); VYKEACompanionData v2 = (VYKEACompanionData)( it.content ); return v1.compareTo( v2 ); } if ( this.type == DataTypes.FLOAT_TYPE ) { return Double.compare( Double.longBitsToDouble( this.contentLong ), Double.longBitsToDouble( it.contentLong ) ); } if ( this.type == DataTypes.MONSTER_TYPE ) { // If we know a monster ID, compare it if ( this.contentLong != 0 || it.contentLong != 0 ) { return this.contentLong < it.contentLong ? -1 : this.contentLong == it.contentLong ? 0 : 1; } // Otherwise, must compare names } if ( this.contentString != null && it.contentString != null ) { return ignoreCase ? this.contentString.compareToIgnoreCase( it.contentString ) : this.contentString.compareTo( it.contentString ); } return -1; } public int count() { return 1; } public void clear() { } public boolean contains( final Value index ) { return false; } @Override public boolean equals( final Object o ) { return o == null || !( o instanceof Value ) ? false : this.compareTo( (Value) o ) == 0; } @Override public int hashCode() { int hash; hash = this.type != null ? this.type.hashCode() : 0; hash = hash + 31 * (int) this.contentLong; hash = hash + 31 * ( this.contentString != null ? this.contentString.hashCode() : 0 ); return hash; } public static String escapeString( String string ) { // Since map_to_file has one record per line with fields // delimited by tabs, string values cannot have newline or tab // characters in them. Escape those characters. And, since we // escape using backslashes, backslash must also be escaped. // // Replace backslashes with \\, newlines with \n, and tabs with \t int length = string.length(); StringBuilder buffer = new StringBuilder( length ); for ( int i = 0; i < length; i++ ) { char c = string.charAt( i ); switch ( c ) { case '\n': buffer.append( "\\n" ); break; case '\t': buffer.append( "\\t" ); break; case '\\': buffer.append( "\\\\" ); break; default: buffer.append( c ); break; } } return buffer.toString(); } public static String unEscapeString( String string ) { int length = string.length(); StringBuilder buffer = new StringBuilder( length ); boolean saw_backslash = false; for ( int i = 0; i < length; i++ ) { char c = string.charAt( i ); if ( !saw_backslash ) { if ( c == '\\' ) { saw_backslash = true; } else { buffer.append( c ); } continue; } switch ( c ) { case 'n': buffer.append( '\n' ); break; case 't': buffer.append( '\t' ); break; default: buffer.append( c ); break; } saw_backslash = false; } if ( saw_backslash ) { buffer.append('\\'); } return buffer.toString(); } public static Value readValue( final Type type, final String string, final String filename, final int line ) { int tnum = type.getType(); if ( tnum == DataTypes.TYPE_STRING ) { return new Value( Value.unEscapeString( string ) ); } Value value = type.parseValue( string, true ); // Validate data and report errors List<String> names = type.getAmbiguousNames( string, value, false ); if ( names != null && names.size() > 1 ) { StringBuilder message = new StringBuilder(); message.append( "Multiple matches for " ); message.append( string ); message.append( "; using " ); message.append( value.toString() ); message.append( " in " ); message.append( Parser.getLineAndFile( filename, line ) ); message.append( ". Clarify by using one of:" ); RequestLogger.printLine( message.toString() ); for ( String str : names ) { RequestLogger.printLine( str ); } } return value; } public String dumpValue() { int type = this.type.getType(); return type == DataTypes.TYPE_STRING ? Value.escapeString( this.contentString ) : type == DataTypes.TYPE_ITEM || type == DataTypes.TYPE_EFFECT ? ( this.contentString.startsWith( "[" ) ? this.contentString : "[" + String.valueOf( this.contentLong ) + "]" + this.contentString ) : this.toString(); } public void dumpValue( final PrintStream writer ) { writer.print( this.dumpValue() ); } public void dump( final PrintStream writer, final String prefix, final boolean compact ) { writer.println( prefix + this.dumpValue() ); } @Override public void print( final PrintStream stream, final int indent ) { Interpreter.indentLine( stream, indent ); stream.println( "<VALUE " + this.getType() + " [" + this.toString() + "]>" ); } public Object toJSON() throws JSONException { if ( this.type.equals( DataTypes.TYPE_BOOLEAN ) ) { return new Boolean( this.contentLong > 0 ); } else if ( this.type.equals( DataTypes.TYPE_INT ) ) { return new Long( this.contentLong ); } else if ( this.type.equals( DataTypes.TYPE_FLOAT ) ) { return new Double( Double.longBitsToDouble( this.contentLong ) ); } else { return this.toString(); } } }