From ec284003947ada630e5c9e9774b14e37aab46959 Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Tue, 16 Apr 2013 00:24:46 -0700 Subject: [PATCH] Switch to new parameter syntax The new syntax is: .param p1, firstParamName which is closer to the existing local syntax: .local v0, someLocal:I --- smali/src/main/antlr3/smaliParser.g | 7 +- smali/src/main/antlr3/smaliTreeWalker.g | 82 ++++++++------ .../org/jf/smali/SmaliMethodParameter.java | 67 ++++++++++++ .../main/java/org/jf/smali/WithRegister.java | 36 +++++++ smali/src/main/jflex/smaliLexer.flex | 4 +- .../resources/LexerTest/DirectiveTest.smali | 6 +- .../resources/LexerTest/DirectiveTest.tokens | 8 +- .../LexerTest/RealSmaliFileTest.smali | 16 +-- .../LexerTest/RealSmaliFileTest.tokens | 48 ++++++--- .../main/java/org/jf/util/LinearSearch.java | 85 +++++++++++++++ .../java/org/jf/util/LinearSearchTest.java | 101 ++++++++++++++++++ 11 files changed, 394 insertions(+), 66 deletions(-) create mode 100644 smali/src/main/java/org/jf/smali/SmaliMethodParameter.java create mode 100644 smali/src/main/java/org/jf/smali/WithRegister.java create mode 100644 util/src/main/java/org/jf/util/LinearSearch.java create mode 100644 util/src/test/java/org/jf/util/LinearSearchTest.java diff --git a/smali/src/main/antlr3/smaliParser.g b/smali/src/main/antlr3/smaliParser.g index ec657571..1ec8a2ac 100644 --- a/smali/src/main/antlr3/smaliParser.g +++ b/smali/src/main/antlr3/smaliParser.g @@ -756,14 +756,13 @@ the annotations. If it turns out that they are parameter annotations, we include add them to the $statements_and_directives::methodAnnotations list*/ parameter_directive @init {List annotations = new ArrayList();} - : PARAMETER_DIRECTIVE - STRING_LITERAL? + : PARAMETER_DIRECTIVE REGISTER COMMA simple_name ({input.LA(1) == ANNOTATION_DIRECTIVE}? annotation {annotations.add($annotation.tree);})* ( END_PARAMETER_DIRECTIVE - -> ^(I_PARAMETER[$start, "I_PARAMETER"] STRING_LITERAL? ^(I_ANNOTATIONS annotation*)) + -> ^(I_PARAMETER[$start, "I_PARAMETER"] REGISTER simple_name ^(I_ANNOTATIONS annotation*)) | /*epsilon*/ {$statements_and_directives::methodAnnotations.addAll(annotations);} - -> ^(I_PARAMETER[$start, "I_PARAMETER"] STRING_LITERAL? ^(I_ANNOTATIONS)) + -> ^(I_PARAMETER[$start, "I_PARAMETER"] REGISTER simple_name ^(I_ANNOTATIONS)) ); ordered_debug_directive diff --git a/smali/src/main/antlr3/smaliTreeWalker.g b/smali/src/main/antlr3/smaliTreeWalker.g index 46577c8c..d530e558 100644 --- a/smali/src/main/antlr3/smaliTreeWalker.g +++ b/smali/src/main/antlr3/smaliTreeWalker.g @@ -63,6 +63,7 @@ import org.jf.dexlib2.immutable.instruction.*; import org.jf.dexlib2.immutable.reference.*; import org.jf.dexlib2.immutable.value.*; import org.jf.dexlib2.util.MethodUtil; +import org.jf.util.LinearSearch; } @@ -341,6 +342,7 @@ method returns[Method ret] int currentAddress; HashMap packedSwitchDeclarations; HashMap sparseSwitchDeclarations; + boolean isStatic; int totalMethodRegisters; int methodParameterRegisters; } @@ -349,11 +351,11 @@ method returns[Method ret] $method::totalMethodRegisters = 0; $method::methodParameterRegisters = 0; int accessFlags = 0; - boolean isStatic = false; $method::labels = new HashMap(); $method::currentAddress = 0; $method::packedSwitchDeclarations = new HashMap(); $method::sparseSwitchDeclarations = new HashMap(); + $method::isStatic = false; } : ^(I_METHOD @@ -361,8 +363,9 @@ method returns[Method ret] access_list { accessFlags = $access_list.value; - isStatic = AccessFlags.STATIC.isSet(accessFlags); - $method::methodParameterRegisters = MethodUtil.getParameterRegisterCount($method_name_and_prototype.parameters, isStatic); + $method::isStatic = AccessFlags.STATIC.isSet(accessFlags); + $method::methodParameterRegisters = + MethodUtil.getParameterRegisterCount($method_name_and_prototype.parameters, $method::isStatic); } (registers_directive { @@ -456,7 +459,7 @@ method returns[Method ret] $ret = new ImmutableMethod( classType, $method_name_and_prototype.name, - $parameters.parameters, + $method_name_and_prototype.parameters, $method_name_and_prototype.returnType, accessFlags, $annotations.annotations, @@ -470,11 +473,20 @@ method_prototype returns[List parameters, String returnType] $parameters = $field_type_list.types; }; -method_name_and_prototype returns[String name, List parameters, String returnType] +method_name_and_prototype returns[String name, List parameters, String returnType] : SIMPLE_NAME method_prototype { $name = $SIMPLE_NAME.text; - $parameters = $method_prototype.parameters; + $parameters = Lists.newArrayList(); + + int paramRegister = 0; + for (String type: $method_prototype.parameters) { + $parameters.add(new SmaliMethodParameter(paramRegister++, type)); + char c = type.charAt(0); + if (c == 'J' || c == 'J') { + paramRegister++; + } + } $returnType = $method_prototype.returnType; }; @@ -594,34 +606,42 @@ address returns[int address] $address = Integer.parseInt($I_ADDRESS.text); }; -parameters[List parameterTypes] returns[List parameters] - @init - { - Iterator parameterIter = parameterTypes.iterator(); - $parameters = Lists.newArrayList(); - } - : ^(I_PARAMETERS (parameter[parameterIter] { $parameters.add($parameter.parameter); })*) - { - String paramType; - while (parameterIter.hasNext()) { - paramType = parameterIter.next(); - $parameters.add(new ImmutableMethodParameter(paramType, null, null)); - } - }; +parameters[List parameters] + : ^(I_PARAMETERS (parameter[parameters])*); -parameter[Iterator parameterTypes] returns[MethodParameter parameter] - : ^(I_PARAMETER string_literal? annotations - { - if (!$parameterTypes.hasNext()) { - throw new SemanticException(input, $I_PARAMETER, "Too many .parameter directives specified."); - } - String type = $parameterTypes.next(); - String name = $string_literal.value; - Set annotations = $annotations.annotations; +parameter[List parameters] + : ^(I_PARAMETER REGISTER SIMPLE_NAME annotations) + { + final int registerNumber = parseRegister_short($REGISTER.text); + int totalMethodRegisters = $method::totalMethodRegisters; + int methodParameterRegisters = $method::methodParameterRegisters; - $parameter = new ImmutableMethodParameter(type, annotations, name); + if (registerNumber >= totalMethodRegisters) { + throw new SemanticException(input, $I_PARAMETER, "Register \%s is larger than the maximum register v\%d " + + "for this method", $REGISTER.text, totalMethodRegisters-1); } - ); + final int indexGuess = registerNumber - (totalMethodRegisters - methodParameterRegisters) - ($method::isStatic?0:1); + + if (indexGuess < 0) { + throw new SemanticException(input, $I_PARAMETER, "Register \%s is not a parameter register.", + $REGISTER.text); + } + + int parameterIndex = LinearSearch.linearSearch(parameters, SmaliMethodParameter.COMPARATOR, + new WithRegister() { public int getRegister() { return indexGuess; } }, + indexGuess); + + if (parameterIndex < 0) { + throw new SemanticException(input, $I_PARAMETER, "Register \%s is the second half of a wide parameter.", + $REGISTER.text); + } + + SmaliMethodParameter methodParameter = parameters.get(parameterIndex); + methodParameter.name = $SIMPLE_NAME.text; + if ($annotations.annotations != null && $annotations.annotations.size() > 0) { + methodParameter.annotations = $annotations.annotations; + } + }; ordered_debug_directives returns[List debugItems] @init {debugItems = Lists.newArrayList();} diff --git a/smali/src/main/java/org/jf/smali/SmaliMethodParameter.java b/smali/src/main/java/org/jf/smali/SmaliMethodParameter.java new file mode 100644 index 00000000..f54c04e6 --- /dev/null +++ b/smali/src/main/java/org/jf/smali/SmaliMethodParameter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. 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 org.jf.smali; + +import com.google.common.collect.ImmutableSet; +import com.google.common.primitives.Ints; +import org.jf.dexlib2.base.BaseMethodParameter; +import org.jf.dexlib2.iface.Annotation; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Comparator; +import java.util.Set; + +public class SmaliMethodParameter extends BaseMethodParameter implements WithRegister { + public final int register; + @Nonnull public final String type; + @Nonnull public Set annotations; + @Nullable public String name; + + public SmaliMethodParameter(int register, @Nonnull String type) { + this.register = register; + this.type = type; + this.annotations = ImmutableSet.of(); + } + + @Override public int getRegister() { return register; } + @Nonnull @Override public String getType() { return type; } + @Nonnull @Override public Set getAnnotations() { return annotations; } + @Nullable @Override public String getName() { return name; } + @Nullable @Override public String getSignature() { return null; } + + public static final Comparator COMPARATOR = new Comparator() { + @Override public int compare(WithRegister o1, WithRegister o2) { + return Ints.compare(o1.getRegister(), o2.getRegister()); + } + }; +} diff --git a/smali/src/main/java/org/jf/smali/WithRegister.java b/smali/src/main/java/org/jf/smali/WithRegister.java new file mode 100644 index 00000000..cd9a48b0 --- /dev/null +++ b/smali/src/main/java/org/jf/smali/WithRegister.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. 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 org.jf.smali; + +public interface WithRegister { + int getRegister(); +} diff --git a/smali/src/main/jflex/smaliLexer.flex b/smali/src/main/jflex/smaliLexer.flex index ac2fe6f1..46b0a2e8 100644 --- a/smali/src/main/jflex/smaliLexer.flex +++ b/smali/src/main/jflex/smaliLexer.flex @@ -254,8 +254,8 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor} ".catch" { return newToken(CATCH_DIRECTIVE); } ".catchall" { return newToken(CATCHALL_DIRECTIVE); } ".line" { return newToken(LINE_DIRECTIVE); } - ".parameter" { return newToken(PARAMETER_DIRECTIVE); } - ".end parameter" { return newToken(END_PARAMETER_DIRECTIVE); } + ".param" { return newToken(PARAMETER_DIRECTIVE); } + ".end param" { return newToken(END_PARAMETER_DIRECTIVE); } ".local" { return newToken(LOCAL_DIRECTIVE); } ".end local" { return newToken(END_LOCAL_DIRECTIVE); } ".restart local" { return newToken(RESTART_LOCAL_DIRECTIVE); } diff --git a/smali/src/test/resources/LexerTest/DirectiveTest.smali b/smali/src/test/resources/LexerTest/DirectiveTest.smali index 5ec54d8b..7ceec71a 100644 --- a/smali/src/test/resources/LexerTest/DirectiveTest.smali +++ b/smali/src/test/resources/LexerTest/DirectiveTest.smali @@ -23,8 +23,8 @@ .catch .catchall .line -.parameter -.end parameter +.param +.end param .local .end local .restart local @@ -52,5 +52,7 @@ . .1234.1234 . +.parameter +.end parameter diff --git a/smali/src/test/resources/LexerTest/DirectiveTest.tokens b/smali/src/test/resources/LexerTest/DirectiveTest.tokens index 0579ad1b..06f4da73 100644 --- a/smali/src/test/resources/LexerTest/DirectiveTest.tokens +++ b/smali/src/test/resources/LexerTest/DirectiveTest.tokens @@ -22,8 +22,8 @@ END_SPARSE_SWITCH_DIRECTIVE(".end sparse-switch") CATCH_DIRECTIVE(".catch") CATCHALL_DIRECTIVE(".catchall") LINE_DIRECTIVE(".line") -PARAMETER_DIRECTIVE(".parameter") -END_PARAMETER_DIRECTIVE(".end parameter") +PARAMETER_DIRECTIVE(".param") +END_PARAMETER_DIRECTIVE(".end param") LOCAL_DIRECTIVE(".local") END_LOCAL_DIRECTIVE(".end local") RESTART_LOCAL_DIRECTIVE(".restart local") @@ -61,4 +61,6 @@ INVALID_TOKEN(".end") INVALID_TOKEN(".") DOUBLE_LITERAL(".1234") DOUBLE_LITERAL(".1234") -INVALID_TOKEN(".") \ No newline at end of file +INVALID_TOKEN(".") +INVALID_TOKEN(".parameter") +INVALID_TOKEN(".end parameter") \ No newline at end of file diff --git a/smali/src/test/resources/LexerTest/RealSmaliFileTest.smali b/smali/src/test/resources/LexerTest/RealSmaliFileTest.smali index 2062f75f..b1bbcbb6 100644 --- a/smali/src/test/resources/LexerTest/RealSmaliFileTest.smali +++ b/smali/src/test/resources/LexerTest/RealSmaliFileTest.smali @@ -100,7 +100,7 @@ # direct methods .method constructor (Lcom/android/internal/telephony/cdma/CDMAPhone;)V .registers 2 - .parameter "phone" + .param p1, phone .prologue .line 42 @@ -112,7 +112,7 @@ .method protected getEFPath(I)Ljava/lang/String; .registers 3 - .parameter "efid" + .param p1, efid .prologue .line 71 @@ -145,7 +145,7 @@ .method CardStateFromRILInt(I)Lcom/android/internal/telephony/IccCardStatus$CardState; .registers 6 - .parameter "state" + .param p1, state .prologue .line 59 @@ -214,11 +214,11 @@ .method public setCallForwardingOption(IILjava/lang/String;ILandroid/os/Message;)V .registers 13 - .parameter "commandInterfaceCFAction" - .parameter "commandInterfaceCFReason" - .parameter "dialingNumber" - .parameter "timerSeconds" - .parameter "onComplete" + .param p1, commandInterfaceCFAction + .param p2, commandInterfaceCFReason + .param p3, dialingNumber + .param p4, timerSeconds + .param p5, onComplete .prologue const/4 v3, 0x1 diff --git a/smali/src/test/resources/LexerTest/RealSmaliFileTest.tokens b/smali/src/test/resources/LexerTest/RealSmaliFileTest.tokens index 562530df..1bd5b0f9 100644 --- a/smali/src/test/resources/LexerTest/RealSmaliFileTest.tokens +++ b/smali/src/test/resources/LexerTest/RealSmaliFileTest.tokens @@ -266,8 +266,10 @@ CLOSE_PAREN(")") VOID_TYPE("V") REGISTERS_DIRECTIVE(".registers") POSITIVE_INTEGER_LITERAL("2") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"phone\"") +PARAMETER_DIRECTIVE(".param") +REGISTER("p1") +COMMA(",") +SIMPLE_NAME("phone") PROLOGUE_DIRECTIVE(".prologue") LINE_DIRECTIVE(".line") POSITIVE_INTEGER_LITERAL("42") @@ -298,8 +300,10 @@ CLOSE_PAREN(")") CLASS_DESCRIPTOR("Ljava/lang/String;") REGISTERS_DIRECTIVE(".registers") POSITIVE_INTEGER_LITERAL("3") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"efid\"") +PARAMETER_DIRECTIVE(".param") +REGISTER("p1") +COMMA(",") +SIMPLE_NAME("efid") PROLOGUE_DIRECTIVE(".prologue") LINE_DIRECTIVE(".line") POSITIVE_INTEGER_LITERAL("71") @@ -369,8 +373,10 @@ CLOSE_PAREN(")") CLASS_DESCRIPTOR("Lcom/android/internal/telephony/IccCardStatus$CardState;") REGISTERS_DIRECTIVE(".registers") POSITIVE_INTEGER_LITERAL("6") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"state\"") +PARAMETER_DIRECTIVE(".param") +REGISTER("p1") +COMMA(",") +SIMPLE_NAME("state") PROLOGUE_DIRECTIVE(".prologue") LINE_DIRECTIVE(".line") POSITIVE_INTEGER_LITERAL("59") @@ -551,16 +557,26 @@ CLOSE_PAREN(")") VOID_TYPE("V") REGISTERS_DIRECTIVE(".registers") POSITIVE_INTEGER_LITERAL("13") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"commandInterfaceCFAction\"") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"commandInterfaceCFReason\"") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"dialingNumber\"") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"timerSeconds\"") -PARAMETER_DIRECTIVE(".parameter") -STRING_LITERAL("\"onComplete\"") +PARAMETER_DIRECTIVE(".param") +REGISTER("p1") +COMMA(",") +SIMPLE_NAME("commandInterfaceCFAction") +PARAMETER_DIRECTIVE(".param") +REGISTER("p2") +COMMA(",") +SIMPLE_NAME("commandInterfaceCFReason") +PARAMETER_DIRECTIVE(".param") +REGISTER("p3") +COMMA(",") +SIMPLE_NAME("dialingNumber") +PARAMETER_DIRECTIVE(".param") +REGISTER("p4") +COMMA(",") +SIMPLE_NAME("timerSeconds") +PARAMETER_DIRECTIVE(".param") +REGISTER("p5") +COMMA(",") +SIMPLE_NAME("onComplete") PROLOGUE_DIRECTIVE(".prologue") INSTRUCTION_FORMAT11n("const/4") REGISTER("v3") diff --git a/util/src/main/java/org/jf/util/LinearSearch.java b/util/src/main/java/org/jf/util/LinearSearch.java new file mode 100644 index 00000000..34336556 --- /dev/null +++ b/util/src/main/java/org/jf/util/LinearSearch.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. 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 org.jf.util; + +import java.util.Comparator; +import java.util.List; + +public class LinearSearch { + /** + * Performs a linear search in a sorted list for key, starting at initialGuess + * + * @param list The sorted list to search + * @param comparator The comparator to use + * @param key The key to search for + * @param initialGuess An initial guess of the location. + * @return If found, the index of the item. If not found, -return + 1 is the index at which the item would be + * inserted + */ + public static int linearSearch(List list, Comparator comparator, T key, int initialGuess) { + int guess = initialGuess; + if (guess >= list.size()) { + guess = list.size()-1; + } + int comparison = comparator.compare(list.get(guess), key); + if (comparison == 0) { + return guess; + } + if (comparison < 0) { + guess++; + while (guess < list.size()) { + comparison = comparator.compare(list.get(guess), key); + if (comparison == 0) { + return guess; + } + if (comparison > 0) { + return -(guess+1); + } + guess++; + } + return -(list.size()+1); + } else { + guess--; + while (guess >= 0) { + comparison = comparator.compare(list.get(guess), key); + if (comparison == 0) { + return guess; + } + if (comparison < 0) { + return -(guess+2); + } + guess--; + } + return -1; + } + } +} diff --git a/util/src/test/java/org/jf/util/LinearSearchTest.java b/util/src/test/java/org/jf/util/LinearSearchTest.java new file mode 100644 index 00000000..76224687 --- /dev/null +++ b/util/src/test/java/org/jf/util/LinearSearchTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. 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 org.jf.util; + +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; +import junit.framework.Assert; +import org.junit.Test; + +import java.util.List; + +public class LinearSearchTest { + @Test + public void testLinearSearch() { + List list = Lists.newArrayList(0, 1, 3, 4); + + doTest(list, 5, 10); + doTest(list, 5, 4); + doTest(list, 5, 3); + doTest(list, 5, 2); + doTest(list, 5, 1); + doTest(list, 5, 0); + + doTest(list, 4, 10); + doTest(list, 4, 4); + doTest(list, 4, 3); + doTest(list, 4, 2); + doTest(list, 4, 1); + doTest(list, 4, 0); + + doTest(list, 3, 10); + doTest(list, 3, 4); + doTest(list, 3, 3); + doTest(list, 3, 2); + doTest(list, 3, 1); + doTest(list, 3, 0); + + doTest(list, 2, 10); + doTest(list, 2, 4); + doTest(list, 2, 3); + doTest(list, 2, 2); + doTest(list, 2, 1); + doTest(list, 2, 0); + + doTest(list, 1, 10); + doTest(list, 1, 4); + doTest(list, 1, 3); + doTest(list, 1, 2); + doTest(list, 1, 1); + doTest(list, 1, 0); + + doTest(list, 0, 10); + doTest(list, 0, 4); + doTest(list, 0, 3); + doTest(list, 0, 2); + doTest(list, 0, 1); + doTest(list, 0, 0); + + doTest(list, -1, 10); + doTest(list, -1, 4); + doTest(list, -1, 3); + doTest(list, -1, 2); + doTest(list, -1, 1); + doTest(list, -1, 0); + } + + private void doTest(List list, int key, int guess) { + int expectedIndex = Ordering.natural().binarySearch(list, key); + + Assert.assertEquals(expectedIndex, LinearSearch.linearSearch(list, Ordering.natural(), key, guess)); + } +}