455 lines
22 KiB
Java
455 lines
22 KiB
Java
/*
|
|
* This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html.
|
|
*
|
|
* This file is a derivative of code released under the terms listed below.
|
|
*
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013,
|
|
* Tobias Blaschke <code@tobiasblaschke.de>
|
|
* 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. The names of the contributors may not 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 com.ibm.wala.dalvik.ipa.callgraph.impl;
|
|
|
|
import java.util.Comparator;
|
|
|
|
import com.ibm.wala.classLoader.IMethod;
|
|
import com.ibm.wala.dalvik.util.AndroidComponent;
|
|
import com.ibm.wala.dalvik.util.AndroidEntryPointLocator.AndroidPossibleEntryPoint;
|
|
import com.ibm.wala.dalvik.util.AndroidTypes;
|
|
import com.ibm.wala.ipa.cha.IClassHierarchy;
|
|
import com.ibm.wala.types.TypeReference;
|
|
import com.ibm.wala.util.strings.Atom;
|
|
/**
|
|
* An AdnroidEntryPoint is basically the same as a DexEntryPoint. The Difference is, that further
|
|
* actions are taken to give a list of EntryPoints a specific order an to add information on how
|
|
* the entrypoints are to be called in a model which represents the usage of an android application.
|
|
* <p>
|
|
* You might want to use AndroidEntryPointLocator to generate a list of all EntryPoints in an
|
|
* android application
|
|
*
|
|
* @see com.ibm.wala.dalvik.util.AndroidEntryPointLocator
|
|
*
|
|
* @author Tobias Blaschke <code@toiasblaschke.de>
|
|
* @since 2013-09-01
|
|
*/
|
|
public class AndroidEntryPoint extends DexEntryPoint {
|
|
//implements AndroidEntryPoint.IExecutionOrder {
|
|
public ExecutionOrder order; // XXX protect?
|
|
|
|
protected AndroidComponent superType;
|
|
|
|
public AndroidEntryPoint(AndroidPossibleEntryPoint p, IMethod method, IClassHierarchy cha, AndroidComponent inComponent) {
|
|
super(method, cha);
|
|
this.order = p.order;
|
|
this.superType = inComponent;
|
|
}
|
|
|
|
public AndroidEntryPoint(AndroidPossibleEntryPoint p, IMethod method, IClassHierarchy cha) {
|
|
super(method, cha);
|
|
this.order = p.order;
|
|
this.superType = AndroidComponent.from(method, cha);
|
|
}
|
|
|
|
public AndroidEntryPoint(ExecutionOrder o, IMethod method, IClassHierarchy cha, AndroidComponent inComponent) {
|
|
this(o, method, cha);
|
|
this.superType = inComponent;
|
|
}
|
|
|
|
public AndroidEntryPoint(ExecutionOrder o, IMethod method, IClassHierarchy cha) {
|
|
super(method, cha);
|
|
if (o == null) {
|
|
throw new IllegalArgumentException("The execution order may not be null.");
|
|
}
|
|
this.order = o;
|
|
this.superType = AndroidComponent.from(method, cha);
|
|
}
|
|
|
|
public AndroidComponent getComponent() {
|
|
return this.superType;
|
|
}
|
|
|
|
/**
|
|
* If the function is defined in a class that extends an Activity.
|
|
*/
|
|
public boolean isActivity() {
|
|
IClassHierarchy cha = getClassHierarchy();
|
|
final TypeReference activity = AndroidTypes.Activity;
|
|
return cha.isSubclassOf(method.getDeclaringClass(), cha.lookupClass(activity));
|
|
}
|
|
|
|
public boolean belongsTo(AndroidComponent compo) {
|
|
if ((compo == AndroidComponent.SERVICE) && (this.superType.equals(AndroidComponent.INTENT_SERVICE))) {
|
|
return true;
|
|
}
|
|
return (this.superType.equals(compo));
|
|
}
|
|
|
|
public boolean isMemberOf(Atom klass) {
|
|
return method.getDeclaringClass().getName().toString().startsWith(klass.toString());
|
|
|
|
//IClassHierarchy cha = getClassHierarchy();
|
|
//final TypeReference type = TypeReference.find(ClassLoaderReference.Primordial, klass.toString());
|
|
//if (type == null) {
|
|
// throw new IllegalArgumentException("Unable to look up " + klass.toString());
|
|
//}
|
|
//return cha.isSubclassOf(method.getDeclaringClass(), cha.lookupClass(type));
|
|
}
|
|
/**
|
|
* Implement this interface to put entitys into the AndroidModel.
|
|
*
|
|
* Currently only AndroidEntryPoints are supportet directly. If you want to add other stuff you might want
|
|
* to subclass AbstractAndroidModel.
|
|
*/
|
|
public interface IExecutionOrder extends Comparable<IExecutionOrder>{
|
|
/**
|
|
* Returns an integer-representation of the ExecutionOrder.
|
|
*/
|
|
public int getOrderValue();
|
|
/**
|
|
* AbstractAndroidModel inserts code at section switches.
|
|
*
|
|
* There are eight hardcoded sections. Sections are derived by rounding the integer-representation.
|
|
*
|
|
* @return the section of this entity
|
|
*/
|
|
public ExecutionOrder getSection();
|
|
}
|
|
|
|
/**
|
|
* AndroidEntryPoints have to be sorted before building the model.
|
|
*/
|
|
public static class ExecutionOrderComperator implements Comparator<AndroidEntryPoint> {
|
|
@Override public int compare(AndroidEntryPoint a, AndroidEntryPoint b) {
|
|
return a.order.compareTo(b.order);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* The section is used to build classes of EntryPoints on how they are to be called.
|
|
*
|
|
* The section is represented by the last label passed before the entity is reached.
|
|
*
|
|
* @return the section of this entrypoint
|
|
*/
|
|
public ExecutionOrder getSection() {
|
|
if (this.order.compareTo(ExecutionOrder.BEFORE_LOOP) == -1) return ExecutionOrder.AT_FIRST;
|
|
if (this.order.compareTo(ExecutionOrder.START_OF_LOOP) == -1) return ExecutionOrder.BEFORE_LOOP;
|
|
if (this.order.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == -1) return ExecutionOrder.START_OF_LOOP;
|
|
if (this.order.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == -1) return ExecutionOrder.MIDDLE_OF_LOOP;
|
|
if (this.order.compareTo(ExecutionOrder.END_OF_LOOP) == -1) return ExecutionOrder.MULTIPLE_TIMES_IN_LOOP;
|
|
if (this.order.compareTo(ExecutionOrder.AFTER_LOOP) == -1) return ExecutionOrder.END_OF_LOOP;
|
|
if (this.order.compareTo(ExecutionOrder.AT_LAST) == -1) return ExecutionOrder.AFTER_LOOP;
|
|
return ExecutionOrder.AT_LAST;
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public int getOrderValue() { return order.getOrderValue(); }
|
|
public int compareTo(AndroidEntryPoint.IExecutionOrder o) {
|
|
return this.order.compareTo(o);
|
|
}
|
|
|
|
|
|
/**
|
|
* The ExecutionOrder is used to partially order EntryPoints.
|
|
*
|
|
* The order has to be understood inclusive! E.g. "after(END_OF_LOOP)" means that the position is __BEFORE__ the
|
|
* loop is actually closed!
|
|
*
|
|
* Before building the model a list of AdroidEntryPoints is to be sorted by that criterion.
|
|
* You can use AndroidEntryPoint.ExecutionOrderComperator for that task.
|
|
*/
|
|
public static class ExecutionOrder implements Comparable<IExecutionOrder>, IExecutionOrder {
|
|
// This is an Enum-Style class
|
|
/** Visit the EntryPoint once at the beginning of the model use that for initialization stuff */
|
|
public final static ExecutionOrder AT_FIRST = new ExecutionOrder(0);
|
|
/** Basicly the same as AT_FIRST but visited after AT_FIRST */
|
|
public final static ExecutionOrder BEFORE_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8);
|
|
/** Visit multiple times (endless) in the loop */
|
|
public final static ExecutionOrder START_OF_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 2);
|
|
/** Basicly the same as START_OF_LOOP */
|
|
public final static ExecutionOrder MIDDLE_OF_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 3);
|
|
/** Do multiple calls in the loop. Visited after MIDDLE_OF_LOOP, before EEN_OF_LOOP */
|
|
public final static ExecutionOrder MULTIPLE_TIMES_IN_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 4);
|
|
/** Things in END_OF_LOOP are acutually part of the loop. Use AFTER_LOOP if you want them executed only once */
|
|
public final static ExecutionOrder END_OF_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 5);
|
|
/** Basicly the same as AT_LAST but visited before */
|
|
public final static ExecutionOrder AFTER_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 6);
|
|
/** Last calls in the model */
|
|
public final static ExecutionOrder AT_LAST = new ExecutionOrder (Integer.MAX_VALUE / 8 * 7);
|
|
/** This value getts used by the detection heuristic - It is not recommended for manual use. */
|
|
public final static ExecutionOrder DEFAULT = MIDDLE_OF_LOOP;
|
|
|
|
private int value;
|
|
/**
|
|
* Unrecommended way to generate the Order based on an Integer.
|
|
*
|
|
* This method is handy when reading back files. In your code you should prefer the methods
|
|
* {@link #after(IExecutionOrder)} and {@link #between(IExecutionOrder, IExecutionOrder)}.
|
|
*/
|
|
public ExecutionOrder(int val) {
|
|
this.value=val;
|
|
}
|
|
/**
|
|
* Unrecommended way to generate the Order based on a Label-String.
|
|
*
|
|
* This method is handy when reading back files. If you want to refer to a label you
|
|
* should prefer the static members.
|
|
*/
|
|
public ExecutionOrder(String label) {
|
|
if (label.equals("AT_FIRST")) { this.value = ExecutionOrder.AT_FIRST.getOrderValue(); return; }
|
|
if (label.equals("BEFORE_LOOP")) { this.value = ExecutionOrder.BEFORE_LOOP.getOrderValue(); return; }
|
|
if (label.equals("START_OF_LOOP")) { this.value = ExecutionOrder.START_OF_LOOP.getOrderValue(); return; }
|
|
if (label.equals("MIDDLE_OF_LOOP")) { this.value = ExecutionOrder.MIDDLE_OF_LOOP.getOrderValue(); return; }
|
|
if (label.equals("MULTIPLE_TIMES_IN_LOOP")) { this.value = ExecutionOrder.MULTIPLE_TIMES_IN_LOOP.getOrderValue(); return; }
|
|
if (label.equals("END_OF_LOOP")) { this.value = ExecutionOrder.END_OF_LOOP.getOrderValue(); return; }
|
|
if (label.equals("AFTER_LOOP")) { this.value = ExecutionOrder.AFTER_LOOP.getOrderValue(); return; }
|
|
if (label.equals("AT_LAST")) { this.value = ExecutionOrder.AT_LAST.getOrderValue(); return; }
|
|
throw new IllegalArgumentException("ExecutionOrder was constructed from an illegal label: " + label);
|
|
}
|
|
/** {@inheritDoc} */
|
|
public int getOrderValue() { return this.value; }
|
|
|
|
/**
|
|
* Use {@link #between(IExecutionOrder, IExecutionOrder)} instead.
|
|
*
|
|
* Does the internal calculations for the placement.
|
|
*/
|
|
private static ExecutionOrder between(int after, int before) {
|
|
if ((before - after) == 1) {
|
|
// It could be ok to warn here instead of throwing: Functions would be located at the same
|
|
// 'slot' in this case.
|
|
// An alternative solution could be to choose other values for before or after.
|
|
throw new ArithmeticException("Precision to low when cascading the ExecutionOrder. You can prevent this error by "
|
|
+ "assigning the ExecutionOrders (e.g. using before() and after()) in a different order.");
|
|
}
|
|
if (after > before) {
|
|
throw new IllegalArgumentException("The requested ordering could not be established due to the after-parameter "
|
|
+ "being greater than the before parameter! after=" + after + " before=" + before);
|
|
}
|
|
return new ExecutionOrder(after + (before - after) / 2);
|
|
}
|
|
|
|
/**
|
|
* Use this to place a call to an EntryPoint between two other EntryPoint calls or ExecutionOrder "labels".
|
|
* between() does not care about section-boundaries by itself!
|
|
*
|
|
* Use {@link between(IExecutionOrder[], IExecutionOrder[])} and use labels as additional placement-information
|
|
* to prevent unexpected misplacement.
|
|
*
|
|
* @param after the call or "label" to be executed before this one
|
|
* @param before the call or "label" to be executed after this one (inclusive)
|
|
* @return A sortable object to represent the execution order
|
|
* @throws ArithmeticException when the precision is no more suitable for further cascading
|
|
* @throws IllegalArgumentException if parameter after is larger than before.
|
|
* @throws NullPointerException if either parameter is null
|
|
*/
|
|
public static ExecutionOrder between(IExecutionOrder after, IExecutionOrder before) {
|
|
if (after == null) {
|
|
throw new NullPointerException("after may not be null");
|
|
}
|
|
if (before == null) {
|
|
throw new NullPointerException("after may not be null");
|
|
}
|
|
return between(after.getOrderValue(), before.getOrderValue());
|
|
}
|
|
|
|
/**
|
|
* Helper for internal use.
|
|
*/
|
|
private static ExecutionOrder between(IExecutionOrder after, int before) {
|
|
return between(after.getOrderValue(), before);
|
|
}
|
|
|
|
/**
|
|
* Use this variant to refer to multiple locations.
|
|
*
|
|
* The minimum / maximum is computed before the placement of the ExecutionOrder.
|
|
*
|
|
* This method is intended to be more robust when changing the position-information of referred-to
|
|
* ExecutionOrders.
|
|
*
|
|
* In any other means it behaves exactly like {@link #between(IExecutionOrder, IExecutionOrder)}.
|
|
*
|
|
* @param after the calls or "labels" to be executed before this one
|
|
* @param before the calls or "labels" to be executed after this one (inclusive)
|
|
* @return A sortable object to represent the execution order
|
|
* @throws ArithmeticException when the precision is no more suitable for further cascading
|
|
* @throws IllegalArgumentException if parameter after is larger than before.
|
|
* @throws NullPointerException if either parameter is null
|
|
*/
|
|
public static ExecutionOrder between(IExecutionOrder[] after, IExecutionOrder[] before) {
|
|
if ((after == null) || (after.length == 0)) {
|
|
throw new NullPointerException("after may not be null or empty array");
|
|
}
|
|
if ((before == null) || (before.length == 0)) {
|
|
throw new NullPointerException("before may not be null or empty array");
|
|
}
|
|
int max = Integer.MIN_VALUE;
|
|
int min = Integer.MAX_VALUE;
|
|
for (IExecutionOrder i : after) if (max < i.getOrderValue()) max=i.getOrderValue();
|
|
for (IExecutionOrder i : before) if (min > i.getOrderValue()) min=i.getOrderValue();
|
|
return between(max, min);
|
|
}
|
|
|
|
public static ExecutionOrder between(IExecutionOrder after, IExecutionOrder[] before) {
|
|
if ((before == null) || (before.length == 0)) {
|
|
throw new NullPointerException("after may not be null or empty array");
|
|
}
|
|
int min = Integer.MAX_VALUE;
|
|
for (IExecutionOrder i : before) if (min > i.getOrderValue()) min=i.getOrderValue();
|
|
return between(after, min);
|
|
}
|
|
|
|
public static ExecutionOrder between(IExecutionOrder[] after, IExecutionOrder before) {
|
|
if ((after == null) || (after.length == 0)) {
|
|
throw new NullPointerException("after may not be null or empty array");
|
|
}
|
|
if (before == null) {
|
|
throw new NullPointerException("before may not be null");
|
|
}
|
|
int max = Integer.MIN_VALUE;
|
|
for (IExecutionOrder i : after) if (max < i.getOrderValue()) max=i.getOrderValue();
|
|
return between(max, before.getOrderValue());
|
|
}
|
|
|
|
/**
|
|
* Place the call in the same section after the given call or "label".
|
|
*
|
|
* @param after the call to be executed before this one or label the call belongs to
|
|
* @return A sortable object to represent the execution order
|
|
* @throws ArithmeticException when the precision is no more suitable for further cascading
|
|
* @throws NullPointerException if the parameter is null
|
|
*/
|
|
public static ExecutionOrder after (IExecutionOrder after) {
|
|
if (after == null) {
|
|
throw new NullPointerException("after may not be null");
|
|
}
|
|
return after(after.getOrderValue());
|
|
}
|
|
|
|
/**
|
|
* Prefer {@link #after(IExecutionOrder)} whenever possible.
|
|
*/
|
|
public static ExecutionOrder after (int after) {
|
|
return between(after, ((after / (Integer.MAX_VALUE / 8)) + 1) * (Integer.MAX_VALUE / 8));
|
|
}
|
|
|
|
/**
|
|
* Use this variant to refer to multiple locations.
|
|
*
|
|
* The maximum is computed before the placement of the ExecutionOrder.
|
|
*
|
|
* @param after the call to be executed before this one or label the call belongs to
|
|
* @return A sortable object to represent the execution order
|
|
* @throws ArithmeticException when the precision is no more suitable for further cascading
|
|
* @throws NullPointerException if the parameter is null
|
|
*/
|
|
public static ExecutionOrder after (IExecutionOrder[] after) {
|
|
if (after == null) {
|
|
throw new NullPointerException("after may not be null");
|
|
}
|
|
|
|
int max = Integer.MIN_VALUE;
|
|
for (IExecutionOrder i : after) if (max < i.getOrderValue()) max=i.getOrderValue();
|
|
return after(max);
|
|
}
|
|
|
|
public static ExecutionOrder directlyBefore(IExecutionOrder before) {
|
|
return new ExecutionOrder(before.getOrderValue() - 1);
|
|
}
|
|
|
|
public static ExecutionOrder directlyAfter(IExecutionOrder before) {
|
|
return new ExecutionOrder(before.getOrderValue() + 1);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public ExecutionOrder getSection() {
|
|
if (this.compareTo(ExecutionOrder.BEFORE_LOOP) == -1) return ExecutionOrder.AT_FIRST;
|
|
if (this.compareTo(ExecutionOrder.START_OF_LOOP) == -1) return ExecutionOrder.BEFORE_LOOP;
|
|
if (this.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == -1) return ExecutionOrder.START_OF_LOOP;
|
|
if (this.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == -1) return ExecutionOrder.MIDDLE_OF_LOOP;
|
|
if (this.compareTo(ExecutionOrder.END_OF_LOOP) == -1) return ExecutionOrder.MULTIPLE_TIMES_IN_LOOP;
|
|
if (this.compareTo(ExecutionOrder.AFTER_LOOP) == -1) return ExecutionOrder.END_OF_LOOP;
|
|
if (this.compareTo(ExecutionOrder.AT_LAST) == -1) return ExecutionOrder.AFTER_LOOP;
|
|
return ExecutionOrder.AT_LAST;
|
|
}
|
|
|
|
|
|
@Override
|
|
public int compareTo(IExecutionOrder o) {
|
|
if (this.value == o.getOrderValue()) return 0;
|
|
if (this.value > o.getOrderValue()) return 1;
|
|
return -1;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (this.compareTo(ExecutionOrder.AT_FIRST) == 0) return "ExecutionOrder.AT_FIRST";
|
|
if (this.compareTo(ExecutionOrder.BEFORE_LOOP) == 0) return "ExecutionOrder.BEFORE_LOOP";
|
|
if (this.compareTo(ExecutionOrder.START_OF_LOOP) == 0) return "ExecutionOrder.START_OF_LOOP";
|
|
if (this.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == 0) return "ExecutionOrder.MIDDLE_OF_LOOP";
|
|
if (this.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == 0) return "ExecutionOrder.MULTIPLE_TIMES_IN_LOOP";
|
|
if (this.compareTo(ExecutionOrder.END_OF_LOOP) == 0) return "ExecutionOrder.END_OF_LOOP";
|
|
if (this.compareTo(ExecutionOrder.AFTER_LOOP) == 0) return "ExecutionOrder.AFTER_LOOP";
|
|
if (this.compareTo(ExecutionOrder.AT_LAST) == 0) return "ExecutionOrder.AT_LAST";
|
|
|
|
if (this.compareTo(ExecutionOrder.BEFORE_LOOP) == -1) return "in section ExecutionOrder.AT_FIRST";
|
|
if (this.compareTo(ExecutionOrder.START_OF_LOOP) == -1) return "in section ExecutionOrder.BEFORE_LOOP";
|
|
if (this.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == -1) return "in section ExecutionOrder.START_OF_LOOP";
|
|
if (this.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == -1) return "in section ExecutionOrder.MIDDLE_OF_LOOP";
|
|
if (this.compareTo(ExecutionOrder.END_OF_LOOP) == -1) return "in section ExecutionOrder.MULTIPLE_TIMES_IN_LOOP";
|
|
if (this.compareTo(ExecutionOrder.AFTER_LOOP) == -1) return "in section ExecutionOrder.END_OF_LOOP";
|
|
if (this.compareTo(ExecutionOrder.AT_LAST) == -1) return "in section ExecutionOrder.AFTER_LOOP";
|
|
return "in section ExecutionOrder.AT_LAST";
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (o instanceof AndroidEntryPoint) {
|
|
AndroidEntryPoint other = (AndroidEntryPoint) o;
|
|
return this.getMethod().equals(other.getMethod());
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return 3 * this.getMethod().hashCode();
|
|
}
|
|
}
|