702 lines
28 KiB
Java
702 lines
28 KiB
Java
/*
|
|
* 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.util;
|
|
|
|
import java.io.Serializable;
|
|
import java.lang.reflect.Constructor;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.ibm.wala.classLoader.CallSiteReference;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.androidModel.parameters.DefaultInstantiationBehavior;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.androidModel.parameters.IInstantiationBehavior;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.androidModel.structure.AbstractAndroidModel;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.androidModel.structure.LoopAndroidModel;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.Intent;
|
|
import com.ibm.wala.ipa.callgraph.Entrypoint;
|
|
import com.ibm.wala.ipa.cha.IClassHierarchy;
|
|
import com.ibm.wala.ipa.summaries.VolatileMethodSummary;
|
|
import com.ibm.wala.types.TypeName;
|
|
import com.ibm.wala.types.TypeReference;
|
|
import com.ibm.wala.util.MonitorUtil.IProgressMonitor;
|
|
import com.ibm.wala.util.NullProgressMonitor;
|
|
import com.ibm.wala.util.collections.HashMapFactory;
|
|
import com.ibm.wala.util.ssa.SSAValueManager;
|
|
import com.ibm.wala.util.ssa.TypeSafeInstructionFactory;
|
|
import com.ibm.wala.util.strings.StringStuff;
|
|
|
|
/**
|
|
* Model configuration and Global list of entrypoints.
|
|
*
|
|
* See the single settings for further description.
|
|
*
|
|
* @author Tobias Blaschke <code@tobiasblaschke.de>
|
|
*/
|
|
public final /* singleton */ class AndroidEntryPointManager implements Serializable {
|
|
private static final Logger logger = LoggerFactory.getLogger(AndroidEntryPointManager.class);
|
|
|
|
public static AndroidEntryPointManager MANAGER = new AndroidEntryPointManager();
|
|
public static List<AndroidEntryPoint> ENTRIES = new ArrayList<AndroidEntryPoint>();
|
|
/**
|
|
* This is TRANSIENT!
|
|
*/
|
|
private transient IInstantiationBehavior instantiation = null;
|
|
|
|
//
|
|
// EntryPoint stuff
|
|
//
|
|
/**
|
|
* Determines if any EntryPoint extends the specified component.
|
|
*/
|
|
public boolean EPContainAny(AndroidComponent compo) {
|
|
for (AndroidEntryPoint ep: ENTRIES) {
|
|
if (ep.belongsTo(compo)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private AndroidEntryPointManager() {}
|
|
|
|
public static void reset() {
|
|
ENTRIES = new ArrayList<AndroidEntryPoint>();
|
|
MANAGER = new AndroidEntryPointManager();
|
|
}
|
|
|
|
public Set<TypeReference> getComponents() {
|
|
if (ENTRIES.isEmpty()) {
|
|
throw new IllegalStateException("No entrypoints loaded yet.");
|
|
}
|
|
|
|
final Set<TypeReference> ret = new HashSet<TypeReference>();
|
|
for (final AndroidEntryPoint ep : ENTRIES) {
|
|
final TypeReference epClass = ep.getMethod().getDeclaringClass().getReference();
|
|
if (AndroidComponent.isAndroidComponent(epClass , ep.getClassHierarchy())) {
|
|
ret.add(epClass);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//
|
|
// General settings
|
|
//
|
|
|
|
private boolean flatComponents = false;
|
|
/**
|
|
* Controlls the initialization of Components.
|
|
*
|
|
* See {@link #setDoFlatComponents(boolean)}.
|
|
*/
|
|
public boolean doFlatComponents() {
|
|
return flatComponents;
|
|
}
|
|
|
|
/**
|
|
* Controlls the initialization of Components.
|
|
*
|
|
* If flatComponents is active an Instance of each Component of the application is generated
|
|
* in the AndroidModelClass. Whenever the model requires a new instance of a component this
|
|
* "globalone" is used.
|
|
*
|
|
* This resembles the seldomly used Flat-Setting of the start of components in Android. Activating
|
|
* this generates a more conservative model.
|
|
*
|
|
* The default is to deactivate this behavior.
|
|
*
|
|
* @return previous setting
|
|
*/
|
|
public boolean setDoFlatComponents(boolean flatComponents) {
|
|
boolean pre = this.flatComponents;
|
|
this.flatComponents = flatComponents;
|
|
return pre;
|
|
}
|
|
|
|
/**
|
|
* Controls the instantiation of variables in the model.
|
|
*
|
|
* See {@link #setInstantiationBehavior(IInstantiationBehavior)}.
|
|
*
|
|
* @param cha Optional parameter given to the IInstantiationBehavior
|
|
* @return DefaultInstantiationBehavior if no other behavior has been set
|
|
*/
|
|
public IInstantiationBehavior getInstantiationBehavior(IClassHierarchy cha) {
|
|
if (this.instantiation == null) {
|
|
this.instantiation = new DefaultInstantiationBehavior(cha);
|
|
}
|
|
return this.instantiation;
|
|
}
|
|
|
|
/**
|
|
* Controls the instantiation of variables in the model.
|
|
*
|
|
* Controlls on which occasions a new instance to a given type shall be generated and when
|
|
* to reuse an existing instance.
|
|
*
|
|
* This also changes the parameters to the later model.
|
|
*
|
|
* The default is DefaultInstantiationBehavior.
|
|
*
|
|
* See {@link #setDoFlatComponents(boolean)} for more instantiation settings that affect components
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.androidModel.parameters.IInstantiationBehavior for more
|
|
* information
|
|
* @return the previous IInstantiationBehavior
|
|
*/
|
|
public IInstantiationBehavior setInstantiationBehavior(IInstantiationBehavior instantiation) {
|
|
final IInstantiationBehavior prev = this.instantiation;
|
|
this.instantiation = instantiation;
|
|
return prev;
|
|
}
|
|
|
|
private transient IProgressMonitor progressMonitor = null;
|
|
/**
|
|
* Can be used to indicate the progress or to cancel operations.
|
|
*
|
|
* @return a NullProgressMonitor or the one set before.
|
|
*/
|
|
public IProgressMonitor getProgressMonitor() {
|
|
if (this.progressMonitor == null) {
|
|
return new NullProgressMonitor();
|
|
} else {
|
|
return this.progressMonitor;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the monitor returned by {@link #getProgressMonitor()}.
|
|
*/
|
|
public IProgressMonitor setProgressMonitor(IProgressMonitor monitor) {
|
|
IProgressMonitor prev = this.progressMonitor;
|
|
this.progressMonitor = monitor;
|
|
return prev;
|
|
}
|
|
|
|
private boolean doBootSequence = true;
|
|
/**
|
|
* Whether to generate a global android environment.
|
|
*
|
|
* See the {@link #setDoBootSequence()} documentation.
|
|
*
|
|
* @return the setting, defaults to true
|
|
*/
|
|
public boolean getDoBootSequence() {
|
|
return this.doBootSequence;
|
|
}
|
|
|
|
/**
|
|
* Whether to generate a global android environment.
|
|
*
|
|
* Inserts some code ath the start of the model to attach some Android-context. This is mainly
|
|
* interesting for inter-application communication.
|
|
*
|
|
* It's possible to analyze android-applications without creating these structures and save
|
|
* some memory. In this case some calls to the OS (like getting the Activity-manager or so)
|
|
* will not be able to be resolved.
|
|
*
|
|
* It is to be noted that the generated information is far from beeing complete.
|
|
*
|
|
* The default is to insert the code.
|
|
*
|
|
* @return the previous setting of doBootSequence
|
|
*/
|
|
public boolean setDoBootSequence(boolean doBootSequence) {
|
|
boolean prev = this.doBootSequence;
|
|
this.doBootSequence = doBootSequence;
|
|
return prev;
|
|
}
|
|
|
|
private Class abstractAndroidModel = LoopAndroidModel.class;
|
|
/**
|
|
* What special handling to insert into the model.
|
|
*
|
|
* At given points in the model (called labels) special code is inserted into it (like loops).
|
|
* This setting controls what code is inserted there.
|
|
*
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.androidModel.structure.AbstractAndroidModel
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.androidModel.structure.SequentialAndroidModel
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.androidModel.structure.LoopAndroidModel
|
|
* @return An object that handles "events" that occur while generating the model.
|
|
* @throws IllegalStateException if initialization fails
|
|
*/
|
|
public AbstractAndroidModel makeModelBehavior(VolatileMethodSummary body, TypeSafeInstructionFactory insts,
|
|
SSAValueManager paramManager, Iterable<? extends Entrypoint> entryPoints) {
|
|
if (abstractAndroidModel == null) {
|
|
return new LoopAndroidModel(body, insts, paramManager, entryPoints);
|
|
} else {
|
|
try {
|
|
final Constructor<AbstractAndroidModel> ctor = this.abstractAndroidModel.getDeclaredConstructor(
|
|
VolatileMethodSummary.class, TypeSafeInstructionFactory.class, SSAValueManager.class,
|
|
Iterable.class);
|
|
if (ctor == null) {
|
|
throw new IllegalStateException("Canot find the constructor of " + this.abstractAndroidModel);
|
|
}
|
|
return (AbstractAndroidModel) ctor.newInstance(body, insts, paramManager, entryPoints);
|
|
} catch (java.lang.InstantiationException e) {
|
|
throw new IllegalStateException(e);
|
|
} catch (java.lang.IllegalAccessException e) {
|
|
throw new IllegalStateException(e);
|
|
} catch (java.lang.reflect.InvocationTargetException e) {
|
|
throw new IllegalStateException(e);
|
|
} catch (java.lang.NoSuchMethodException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The behavior set using setModelBehavior(Class).
|
|
*
|
|
* Use {@link makeModelBehavior(VolatileMethodSummary, JavaInstructionFactory, AndroidModelParameterManager, Iterable<? extends Entrypoint>}
|
|
* to retrieve an instance of this class.
|
|
*
|
|
* If no class was set it returns null, makeModelBehavior will generate a LoopAndroidModel by default.
|
|
*
|
|
* @return null or the class set using setModelBehavior
|
|
*/
|
|
public Class getModelBehavior() {
|
|
return this.abstractAndroidModel;
|
|
}
|
|
|
|
/**
|
|
* Set the class instantiated by makeModelBehavior.
|
|
*
|
|
* @throws IllgealArgumentException if the abstractAndroidModel does not subclass AbstractAndroidModel
|
|
*/
|
|
public void setModelBehavior(Class abstractAndroidModel) {
|
|
if (abstractAndroidModel == null) {
|
|
throw new IllegalArgumentException("abstractAndroidModel may not be null. Use SequentialAndroidModel " +
|
|
"if no special handling shall be inserted.");
|
|
}
|
|
if (! AbstractAndroidModel.class.isAssignableFrom(abstractAndroidModel)) {
|
|
throw new IllegalArgumentException("The given argument abstractAndroidModel does not subclass " +
|
|
"AbtractAndroidModel");
|
|
}
|
|
this.abstractAndroidModel = abstractAndroidModel;
|
|
}
|
|
|
|
//
|
|
// Propertys of the analyzed app
|
|
//
|
|
private transient String pack = null;
|
|
|
|
/**
|
|
* Set the package of the analyzed application.
|
|
*
|
|
* Setting the package of the application is completely optional. However if you do it it helps
|
|
* determining whether an Intent has an internal target.
|
|
*
|
|
* If a AndroidManifest.xml is read this getts set automaticly.
|
|
*
|
|
* @param pack The package of the analyzed application
|
|
* @throws IllegalArgumentException if the package has already been set and the value of the
|
|
* packages differ. Or if the given package is null.
|
|
*/
|
|
public void setPackage(String pack) {
|
|
if (pack == null) {
|
|
throw new IllegalArgumentException("Setting the package to null is disallowed.");
|
|
}
|
|
if ((! pack.startsWith("L") || pack.contains("."))) {
|
|
pack = StringStuff.deployment2CanonicalTypeString(pack);
|
|
}
|
|
if (this.pack == null) {
|
|
logger.info("Setting the package to {}", pack);
|
|
this.pack = pack;
|
|
} else if (!(this.pack.equals(pack))) {
|
|
throw new IllegalArgumentException("The already set package " + this.pack + " and " + pack +
|
|
" differ. You can only set pack once.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the package of the analyzed app.
|
|
*
|
|
* This only returns a value other than null if the package has explicitly been set using
|
|
* setPackage (which is for example called when reading in the Manifest).
|
|
*
|
|
* If you didn't read the manifest you can still try and retrieve the package name using
|
|
* guessPackage().
|
|
*
|
|
* See: {@link #setPackage(String)}
|
|
*
|
|
* @return The package or null if it was indeterminable.
|
|
* @see guessPacakge()
|
|
*/
|
|
public String getPackage() {
|
|
if (this.pack == null) {
|
|
logger.warn("Returning null as package");
|
|
return null;
|
|
} else {
|
|
return this.pack;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the package of the analyzed app.
|
|
*
|
|
* If the package has been set using setPackage() return this value. Else try and determine
|
|
* the package based on the first entrypoint.
|
|
*
|
|
* @return The package or null if it was indeterminable.
|
|
* @see getPackage()
|
|
*/
|
|
public String guessPackage() {
|
|
if (this.pack != null) {
|
|
return this.pack;
|
|
} else {
|
|
if (ENTRIES.isEmpty()) {
|
|
logger.error("guessPackage() called when no entrypoints had been set");
|
|
return null;
|
|
}
|
|
final String first = ENTRIES.get(0).getMethod().getReference().getDeclaringClass().getName().getPackage().toString();
|
|
// TODO: Iterate all?
|
|
return first;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Intent stuff
|
|
//
|
|
|
|
/**
|
|
* Overrides Intents.
|
|
*
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.Intent
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.IntentContextInterpreter
|
|
*/
|
|
public final Map<Intent, Intent> overrideIntents = HashMapFactory.make();
|
|
|
|
/**
|
|
* Set more information to an Intent.
|
|
*
|
|
* You can call this method multiple times on the same Intent as long as you don't lower the associated
|
|
* information. So if you only want to change a specific value of it it is more safe to retrieve the Intent
|
|
* first and union it yourself before registering it.
|
|
*
|
|
* @param intent An Intent with more or the same information as known to the system before.
|
|
* @throws IllegalArgumentException if you lower the information on an already registered Intent or the
|
|
* information is incompatible.
|
|
* @see registerIntentForce()
|
|
*/
|
|
public void registerIntent(Intent intent) {
|
|
if (overrideIntents.containsKey(intent)) {
|
|
final Intent original = overrideIntents.get(intent);
|
|
final Intent.IntentType oriType = original.getType();
|
|
final Intent.IntentType newType = intent.getType();
|
|
|
|
if ((newType == Intent.IntentType.UNKNOWN_TARGET) && (oriType != Intent.IntentType.UNKNOWN_TARGET)) {
|
|
throw new IllegalArgumentException("You are lowering information on the Intent-Target of the " +
|
|
"Intent " + original + " from " + oriType + " to " + newType + ". Use registerIntentForce()" +
|
|
"If you are sure you want to do this!");
|
|
} else if (oriType != newType) {
|
|
throw new IllegalArgumentException("You are changing the Intents target to a contradicting one! " +
|
|
newType + "(new) is incompatible to " + oriType + "(before). On Intent " + intent +
|
|
". Use registerIntentForce() if you are sure you want to do this!");
|
|
}
|
|
|
|
// TODO: Add actual target to the Intent and compare these?
|
|
registerIntentForce(intent);
|
|
} else {
|
|
registerIntentForce(intent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set intent possibly overwriting more specific information.
|
|
*
|
|
* If you are sure that you want to override an existing registered Intent with information that is
|
|
* possibly incompatible with the information originally set.
|
|
*/
|
|
public void registerIntentForce(Intent intent) {
|
|
if (intent == null) {
|
|
throw new IllegalArgumentException("The given Intent is null");
|
|
}
|
|
|
|
logger.info("Register Intent {}", intent);
|
|
// Looks a bit weired but works as Intents are only matched based on their action and uri
|
|
overrideIntents.put(intent, intent);
|
|
}
|
|
|
|
/**
|
|
* Override target of an Intent (or add an alias).
|
|
*
|
|
* Use this for example to add an internal target to an implicit Intent, add an alias to an Intent,
|
|
* resolve a System-name to an internal Intent, do weird stuff...
|
|
*
|
|
* None of the Intents have to be registered before. However if the source is registered you may not
|
|
* lower information on it.
|
|
*
|
|
* Currently only one target to an Intent is supported! If you want to emulate multiple Targets you
|
|
* may have to add a synthetic class and register it as an Intent. If the target is not set to Internal
|
|
* multiple targets may implicitly emulated. See the Documentation for these targets for detail.
|
|
*
|
|
* If you only intend to make an Intent known see {@link #registerIntent(Intent)}.
|
|
*
|
|
* @param from the Intent to override
|
|
* @param to the new Intent to resolve once 'from' is seen
|
|
* @see setOverrideForce()
|
|
* @throws IllegalArgumentException if you override an Intent with itself
|
|
*/
|
|
public void setOverride(Intent from, Intent to) {
|
|
if (from == null) {
|
|
throw new IllegalArgumentException("The Intent given as 'from' is null");
|
|
}
|
|
if (to == null) {
|
|
throw new IllegalArgumentException("The Intent given as 'to' is null");
|
|
}
|
|
if (from.equals(to)) {
|
|
throw new IllegalArgumentException("You cannot override an Intent with itself! If you want to " +
|
|
"alter Information on an Intent use registerIntent (you may register it multiple times).");
|
|
}
|
|
|
|
if (overrideIntents.containsKey(from)) {
|
|
final Intent ori = overrideIntents.get(from);
|
|
final Intent source;
|
|
if (ori == from) {
|
|
// The Intent has been registered before. Set the registered variant as source so Information
|
|
// that may have been altered is not lost. Not that it would matter now...
|
|
final Intent.IntentType oriType = ori.getType();
|
|
final Intent.IntentType newType = from.getType();
|
|
if ((newType == Intent.IntentType.UNKNOWN_TARGET) && (oriType != Intent.IntentType.UNKNOWN_TARGET)) {
|
|
// TODO: Test target resolvability
|
|
source = ori;
|
|
} else {
|
|
source = from;
|
|
}
|
|
} else {
|
|
source = from;
|
|
}
|
|
|
|
// Make sure the new target is not less specific than a known override
|
|
final Intent original = overrideIntents.get(to);
|
|
final Intent.IntentType oriType = original.getType();
|
|
final Intent.IntentType newType = to.getType();
|
|
|
|
if ((newType == Intent.IntentType.UNKNOWN_TARGET) && (oriType != Intent.IntentType.UNKNOWN_TARGET)) {
|
|
throw new IllegalArgumentException("You are lowering information on the Intent-Target of the " +
|
|
"Intent " + original + " from " + oriType + " to " + newType + ". Use setOverrideForce()" +
|
|
"If you are sure you want to do this!");
|
|
} else if (oriType != newType) {
|
|
throw new IllegalArgumentException("You are changing the Intents target to a contradicting one! " +
|
|
newType + "(new) is incompatible to " + oriType + "(before). On Intent " + to +
|
|
". Use setOverrideForce() if you are sure you want to do this!");
|
|
}
|
|
|
|
// TODO: Check resolvable Target is not overridden with unresolvable one
|
|
|
|
setOverrideForce(source, to);
|
|
} else {
|
|
setOverrideForce(from, to);
|
|
}
|
|
}
|
|
|
|
public static final Map<Intent, Intent> DEFAULT_INTENT_OVERRIDES = new HashMap<Intent, Intent>();
|
|
static {
|
|
DEFAULT_INTENT_OVERRIDES.put(
|
|
new AndroidSettingFactory.ExternalIntent("Landroid/intent/action/DIAL"),
|
|
new AndroidSettingFactory.ExternalIntent("Landroid/intent/action/DIAL"));
|
|
}
|
|
|
|
/**
|
|
* Set multiple overrides at the same time.
|
|
*
|
|
* See {@link #setOverride(Intent, Intent)}.
|
|
*/
|
|
public void setOverrides(Map<Intent, Intent> overrides) {
|
|
for (final Intent from : overrides.keySet()) {
|
|
final Intent to = overrides.get(from);
|
|
if (from.equals(to)) {
|
|
registerIntent(to);
|
|
} else {
|
|
setOverride(from, overrides.get(from));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Just throw in the override.
|
|
*
|
|
* See {@link #setOverride(Intent, Intent)}.
|
|
*/
|
|
public void setOverrideForce(Intent from, Intent to) {
|
|
if (from == null) {
|
|
throw new IllegalArgumentException("The Intent given as 'from' is null");
|
|
}
|
|
if (to == null) {
|
|
throw new IllegalArgumentException("The Intent given as 'to' is null");
|
|
}
|
|
|
|
logger.info("Override Intent {} to {}", from, to);
|
|
overrideIntents.put(from, to);
|
|
}
|
|
|
|
/**
|
|
* Get Intent with applied overrides.
|
|
*
|
|
* If there are no overrides or the Intent is not registered return it as is.
|
|
*
|
|
* See {@link #setOverride(Intent, Intent)}.
|
|
* See {@link #registerIntent(Intent)}.
|
|
*
|
|
* @param intent The intent to resolve
|
|
* @return where to resolve it to or the given intent if no information is available
|
|
*
|
|
* @todo TODO: Malicious Intent-Table could cause endless loops
|
|
*/
|
|
public Intent getIntent(Intent intent) {
|
|
if (overrideIntents.containsKey(intent)) {
|
|
Intent ret = overrideIntents.get(intent);
|
|
while (!(ret.equals(intent))) {
|
|
// Follow the chain of overrides
|
|
if (!overrideIntents.containsKey(intent)) {
|
|
logger.info("Resolved {} to {}", intent, ret);
|
|
return ret;
|
|
} else {
|
|
logger.debug("Resolving {} hop over {}", intent, ret);
|
|
final Intent old = ret;
|
|
ret = overrideIntents.get(ret);
|
|
|
|
if (ret == old) { // Yes, ==
|
|
// This is an evil hack(tm). I should fix the Intent-Table!
|
|
logger.warn("Malformend Intent-Table, staying with " + ret + " for " + intent);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
ret = overrideIntents.get(ret); // Once again to get Info set in register
|
|
logger.info("Resolved {} to {}", intent, ret);
|
|
return ret;
|
|
} else {
|
|
logger.info("No information on {} hash: {}", intent, intent.hashCode());
|
|
for (Intent known : overrideIntents.keySet()) {
|
|
logger.debug("Known Intents: {} hash: {}", known, known.hashCode());
|
|
}
|
|
return intent;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Searches Intent specifications for the occurrence of clazz.
|
|
*
|
|
* @return the intent is registered or there exists an override.
|
|
*/
|
|
public boolean existsIntentFor(TypeName clazz) {
|
|
for (Intent i : overrideIntents.keySet()) {
|
|
if (i.getAction().toString().equals(clazz.toString())) { // XXX toString-Matches are shitty
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (Intent i : overrideIntents.values()) {
|
|
if (i.getAction().toString().equals(clazz.toString())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private transient Map<CallSiteReference, Intent> seenIntentCalls = HashMapFactory.make();
|
|
/**
|
|
* DO NOT CALL! - This is for IntentContextSelector.
|
|
*
|
|
* Add information that an Intent was called to the later summary. This is for information-purpose
|
|
* only and does not change any behavior.
|
|
*
|
|
* Intents are added as seen - without any resolved overrides.
|
|
*/
|
|
public void addCallSeen(CallSiteReference from, Intent intent) {
|
|
seenIntentCalls.put(from, intent);
|
|
}
|
|
|
|
/**
|
|
* Return all Sites, that start Components based on Intents.
|
|
*/
|
|
public Map<CallSiteReference, Intent> getSeen() {
|
|
return seenIntentCalls; // No need to make read-only
|
|
}
|
|
|
|
private boolean allowIntentRerouting = true;
|
|
|
|
/**
|
|
* Controll modification of an Intents target after construction.
|
|
*
|
|
* After an Intent has been constructed its target may be changed using functions like
|
|
* setAction or setComponent.
|
|
* This setting controlls the behavior of the model on occurrence of such a function:
|
|
*
|
|
* If set to false the Intent will be marked as unresolvable.
|
|
*
|
|
* If set to true the first occurrence of such a function changes the target of the
|
|
* Intent unless:
|
|
*
|
|
* * The Intent was explicit and the new action is not: The call gets ignored
|
|
* * The Intent was explicit and the new target is explicit: It becomes unresolvable
|
|
* * It's the second occurrence of such a function: It becomes unresolvable
|
|
* * It was resolvable: It becomes unresolvable
|
|
*
|
|
* The default is to activate this behavior.
|
|
*
|
|
* @param allow Allow rerouting as described
|
|
* @return previous setting
|
|
*/
|
|
public boolean setAllowIntentRerouting(boolean allow) {
|
|
boolean prev = allowIntentRerouting;
|
|
allowIntentRerouting = allow;
|
|
return prev;
|
|
}
|
|
|
|
/**
|
|
* Controll modification of an Intents target after construction.
|
|
*
|
|
* See: {@link #setAllowIntentRerouting(boolean)}.
|
|
*
|
|
* @return the set behavior or true, the default
|
|
*/
|
|
public boolean isAllowIntentRerouting() {
|
|
return allowIntentRerouting;
|
|
}
|
|
|
|
/**
|
|
* Last 8 digits encode the date.
|
|
*/
|
|
private final static long serialVersionUID = 8740020131212L;
|
|
}
|