blob: 8f99237491bccd0dbf39c334f2f307d594e78fd2 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gre;
import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX;
import static com.android.SdkConstants.VIEW_MERGE;
import static com.android.SdkConstants.VIEW_TAG;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IDragElement;
import com.android.ide.common.api.IGraphics;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.IViewRule;
import com.android.ide.common.api.InsertType;
import com.android.ide.common.api.Point;
import com.android.ide.common.api.Rect;
import com.android.ide.common.api.RuleAction;
import com.android.ide.common.api.SegmentType;
import com.android.ide.common.layout.ViewRule;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GCWrapper;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleElement;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IProject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* The rule engine manages the layout rules and interacts with them.
* There's one {@link RulesEngine} instance per layout editor.
* Each instance has 2 sets of rules: the static ADT rules (shared across all instances)
* and the project specific rules (local to the current instance / layout editor).
*/
public class RulesEngine {
private final IProject mProject;
private final Map<Object, IViewRule> mRulesCache = new HashMap<Object, IViewRule>();
/**
* The type of any upcoming node manipulations performed by the {@link IViewRule}s.
* When actions are performed in the tool (like a paste action, or a drag from palette,
* or a drag move within the canvas, etc), these are different types of inserts,
* and we don't want to have the rules track them closely (and pass them back to us
* in the {@link INode#insertChildAt} methods etc), so instead we track the state
* here on behalf of the currently executing rule.
*/
private InsertType mInsertType = InsertType.CREATE;
/**
* Per-project loader for custom view rules
*/
private RuleLoader mRuleLoader;
private ClassLoader mUserClassLoader;
/**
* The editor which owns this {@link RulesEngine}
*/
private final GraphicalEditorPart mEditor;
/**
* Creates a new {@link RulesEngine} associated with the selected project.
* <p/>
* The rules engine will look in the project for a tools jar to load custom view rules.
*
* @param editor the editor which owns this {@link RulesEngine}
* @param project A non-null open project.
*/
public RulesEngine(GraphicalEditorPart editor, IProject project) {
mProject = project;
mEditor = editor;
mRuleLoader = RuleLoader.get(project);
}
/**
* Returns the {@link IProject} on which the {@link RulesEngine} was created.
*/
public IProject getProject() {
return mProject;
}
/**
* Returns the {@link GraphicalEditorPart} for which the {@link RulesEngine} was
* created.
*
* @return the associated editor
*/
public GraphicalEditorPart getEditor() {
return mEditor;
}
/**
* Called by the owner of the {@link RulesEngine} when it is going to be disposed.
* This frees some resources, such as the project's folder monitor.
*/
public void dispose() {
clearCache();
}
/**
* Invokes {@link IViewRule#getDisplayName()} on the rule matching the specified element.
*
* @param element The view element to target. Can be null.
* @return Null if the rule failed, there's no rule or the rule does not want to override
* the display name. Otherwise, a string as returned by the rule.
*/
public String callGetDisplayName(UiViewElementNode element) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(element);
if (rule != null) {
try {
return rule.getDisplayName();
} catch (Exception e) {
AdtPlugin.log(e, "%s.getDisplayName() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
/**
* Invokes {@link IViewRule#addContextMenuActions(List, INode)} on the rule matching the specified element.
*
* @param selectedNode The node selected. Never null.
* @return Null if the rule failed, there's no rule or the rule does not provide
* any custom menu actions. Otherwise, a list of {@link RuleAction}.
*/
@Nullable
public List<RuleAction> callGetContextMenu(NodeProxy selectedNode) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(selectedNode.getNode());
if (rule != null) {
try {
mInsertType = InsertType.CREATE;
List<RuleAction> actions = new ArrayList<RuleAction>();
rule.addContextMenuActions(actions, selectedNode);
Collections.sort(actions);
return actions;
} catch (Exception e) {
AdtPlugin.log(e, "%s.getContextMenu() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
/**
* Calls the selected node to return its default action
*
* @param selectedNode the node to apply the action to
* @return the default action id
*/
public String callGetDefaultActionId(@NonNull NodeProxy selectedNode) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(selectedNode.getNode());
if (rule != null) {
try {
mInsertType = InsertType.CREATE;
return rule.getDefaultActionId(selectedNode);
} catch (Exception e) {
AdtPlugin.log(e, "%s.getDefaultAction() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
/**
* Invokes {@link IViewRule#addLayoutActions(List, INode, List)} on the rule
* matching the specified element.
*
* @param actions The list of actions to add layout actions into
* @param parentNode The layout node
* @param children The selected children of the node, if any (used to
* initialize values of child layout controls, if applicable)
* @return Null if the rule failed, there's no rule or the rule does not
* provide any custom menu actions. Otherwise, a list of
* {@link RuleAction}.
*/
public List<RuleAction> callAddLayoutActions(List<RuleAction> actions,
NodeProxy parentNode, List<NodeProxy> children ) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(parentNode.getNode());
if (rule != null) {
try {
mInsertType = InsertType.CREATE;
rule.addLayoutActions(actions, parentNode, children);
} catch (Exception e) {
AdtPlugin.log(e, "%s.getContextMenu() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
/**
* Invokes {@link IViewRule#getSelectionHint(INode, INode)}
* on the rule matching the specified element.
*
* @param parentNode The parent of the node selected. Never null.
* @param childNode The child node that was selected. Never null.
* @return a list of strings to be displayed, or null or empty to display nothing
*/
public List<String> callGetSelectionHint(NodeProxy parentNode, NodeProxy childNode) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(parentNode.getNode());
if (rule != null) {
try {
return rule.getSelectionHint(parentNode, childNode);
} catch (Exception e) {
AdtPlugin.log(e, "%s.getSelectionHint() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
public void callPaintSelectionFeedback(GCWrapper gcWrapper, NodeProxy parentNode,
List<? extends INode> childNodes, Object view) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(parentNode.getNode());
if (rule != null) {
try {
rule.paintSelectionFeedback(gcWrapper, parentNode, childNodes, view);
} catch (Exception e) {
AdtPlugin.log(e, "%s.callPaintSelectionFeedback() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
}
/**
* Called when the d'n'd starts dragging over the target node.
* If interested, returns a DropFeedback passed to onDrop/Move/Leave/Paint.
* If not interested in drop, return false.
* Followed by a paint.
*/
public DropFeedback callOnDropEnter(NodeProxy targetNode,
Object targetView, IDragElement[] elements) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(targetNode.getNode());
if (rule != null) {
try {
return rule.onDropEnter(targetNode, targetView, elements);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onDropEnter() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
/**
* Called after onDropEnter.
* Returns a DropFeedback passed to onDrop/Move/Leave/Paint (typically same
* as input one).
*/
public DropFeedback callOnDropMove(NodeProxy targetNode,
IDragElement[] elements,
DropFeedback feedback,
Point where) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(targetNode.getNode());
if (rule != null) {
try {
return rule.onDropMove(targetNode, elements, feedback, where);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onDropMove() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
/**
* Called when drop leaves the target without actually dropping
*/
public void callOnDropLeave(NodeProxy targetNode,
IDragElement[] elements,
DropFeedback feedback) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(targetNode.getNode());
if (rule != null) {
try {
rule.onDropLeave(targetNode, elements, feedback);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onDropLeave() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
}
/**
* Called when drop is released over the target to perform the actual drop.
*/
public void callOnDropped(NodeProxy targetNode,
IDragElement[] elements,
DropFeedback feedback,
Point where,
InsertType insertType) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(targetNode.getNode());
if (rule != null) {
try {
mInsertType = insertType;
rule.onDropped(targetNode, elements, feedback, where);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onDropped() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
}
/**
* Called when a paint has been requested via DropFeedback.
*/
public void callDropFeedbackPaint(IGraphics gc,
NodeProxy targetNode,
DropFeedback feedback) {
if (gc != null && feedback != null && feedback.painter != null) {
try {
feedback.painter.paint(gc, targetNode, feedback);
} catch (Exception e) {
AdtPlugin.log(e, "DropFeedback.painter failed: %s",
e.toString());
}
}
}
/**
* Called when pasting elements in an existing document on the selected target.
*
* @param targetNode The first node selected.
* @param targetView The view object for the target node, or null if not known
* @param pastedElements The elements being pasted.
* @return the parent node the paste was applied into
*/
public NodeProxy callOnPaste(NodeProxy targetNode, Object targetView,
SimpleElement[] pastedElements) {
// Find a target which accepts children. If you for example select a button
// and attempt to paste, this will reselect the parent of the button as the paste
// target. (This is a loop rather than just checking the direct parent since
// we will soon ask each child whether they are *willing* to accept the new child.
// A ScrollView for example, which only accepts one child, might also say no
// and delegate to its parent in turn.
INode parent = targetNode;
while (parent instanceof NodeProxy) {
NodeProxy np = (NodeProxy) parent;
if (np.getNode() != null && np.getNode().getDescriptor() != null) {
ElementDescriptor descriptor = np.getNode().getDescriptor();
if (descriptor.hasChildren()) {
targetNode = np;
break;
}
}
parent = parent.getParent();
}
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(targetNode.getNode());
if (rule != null) {
try {
mInsertType = InsertType.PASTE;
rule.onPaste(targetNode, targetView, pastedElements);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onPaste() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
return targetNode;
}
// ---- Resize operations ----
public DropFeedback callOnResizeBegin(NodeProxy child, NodeProxy parent, Rect newBounds,
SegmentType horizontalEdge, SegmentType verticalEdge, Object childView,
Object parentView) {
IViewRule rule = loadRule(parent.getNode());
if (rule != null) {
try {
return rule.onResizeBegin(child, parent, horizontalEdge, verticalEdge,
childView, parentView);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onResizeBegin() failed: %s", rule.getClass().getSimpleName(),
e.toString());
}
}
return null;
}
public void callOnResizeUpdate(DropFeedback feedback, NodeProxy child, NodeProxy parent,
Rect newBounds, int modifierMask) {
IViewRule rule = loadRule(parent.getNode());
if (rule != null) {
try {
rule.onResizeUpdate(feedback, child, parent, newBounds, modifierMask);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onResizeUpdate() failed: %s", rule.getClass().getSimpleName(),
e.toString());
}
}
}
public void callOnResizeEnd(DropFeedback feedback, NodeProxy child, NodeProxy parent,
Rect newBounds) {
IViewRule rule = loadRule(parent.getNode());
if (rule != null) {
try {
rule.onResizeEnd(feedback, child, parent, newBounds);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onResizeEnd() failed: %s", rule.getClass().getSimpleName(),
e.toString());
}
}
}
// ---- Creation customizations ----
/**
* Invokes the create hooks ({@link IViewRule#onCreate},
* {@link IViewRule#onChildInserted} when a new child has been created/pasted/moved, and
* is inserted into a given parent. The parent may be null (for example when rendering
* top level items for preview).
*
* @param editor the XML editor to apply edits to the model for (performed by view
* rules)
* @param parentNode the parent XML node, or null if unknown
* @param childNode the XML node of the new node, never null
* @param overrideInsertType If not null, specifies an explicit insert type to use for
* edits made during the customization
*/
public void callCreateHooks(
AndroidXmlEditor editor,
NodeProxy parentNode, NodeProxy childNode,
InsertType overrideInsertType) {
IViewRule parentRule = null;
if (parentNode != null) {
UiViewElementNode parentUiNode = parentNode.getNode();
parentRule = loadRule(parentUiNode);
}
if (overrideInsertType != null) {
mInsertType = overrideInsertType;
}
UiViewElementNode newUiNode = childNode.getNode();
IViewRule childRule = loadRule(newUiNode);
if (childRule != null || parentRule != null) {
callCreateHooks(editor, mInsertType, parentRule, parentNode,
childRule, childNode);
}
}
private static void callCreateHooks(
final AndroidXmlEditor editor, final InsertType insertType,
final IViewRule parentRule, final INode parentNode,
final IViewRule childRule, final INode newNode) {
// Notify the parent about the new child in case it wants to customize it
// (For example, a ScrollView parent can go and set all its children's layout params to
// fill the parent.)
if (!editor.isEditXmlModelPending()) {
editor.wrapEditXmlModel(new Runnable() {
@Override
public void run() {
callCreateHooks(editor, insertType,
parentRule, parentNode, childRule, newNode);
}
});
return;
}
if (parentRule != null) {
parentRule.onChildInserted(newNode, parentNode, insertType);
}
// Look up corresponding IViewRule, and notify the rule about
// this create action in case it wants to customize the new object.
// (For example, a rule for TabHosts can go and create a default child tab
// when you create it.)
if (childRule != null) {
childRule.onCreate(newNode, parentNode, insertType);
}
if (parentNode != null) {
((NodeProxy) parentNode).applyPendingChanges();
}
}
/**
* Set the type of insert currently in progress
*
* @param insertType the insert type to use for the next operation
*/
public void setInsertType(InsertType insertType) {
mInsertType = insertType;
}
/**
* Return the type of insert currently in progress
*
* @return the type of insert currently in progress
*/
public InsertType getInsertType() {
return mInsertType;
}
// ---- Deletion ----
public void callOnRemovingChildren(NodeProxy parentNode,
List<INode> children) {
if (parentNode != null) {
UiViewElementNode parentUiNode = parentNode.getNode();
IViewRule parentRule = loadRule(parentUiNode);
if (parentRule != null) {
try {
parentRule.onRemovingChildren(children, parentNode,
mInsertType == InsertType.MOVE_WITHIN);
} catch (Exception e) {
AdtPlugin.log(e, "%s.onDispose() failed: %s",
parentRule.getClass().getSimpleName(),
e.toString());
}
}
}
}
// ---- private ---
/**
* Returns the descriptor for the base View class.
* This could be null if the SDK or the given platform target hasn't loaded yet.
*/
private ViewElementDescriptor getBaseViewDescriptor() {
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IAndroidTarget target = currentSdk.getTarget(mProject);
if (target != null) {
AndroidTargetData data = currentSdk.getTargetData(target);
return data.getLayoutDescriptors().getBaseViewDescriptor();
}
}
return null;
}
/**
* Clear the Rules cache. Calls onDispose() on each rule.
*/
private void clearCache() {
// The cache can contain multiple times the same rule instance for different
// keys (e.g. the UiViewElementNode key vs. the FQCN string key.) So transfer
// all values to a unique set.
HashSet<IViewRule> rules = new HashSet<IViewRule>(mRulesCache.values());
mRulesCache.clear();
for (IViewRule rule : rules) {
if (rule != null) {
try {
rule.onDispose();
} catch (Exception e) {
AdtPlugin.log(e, "%s.onDispose() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
}
}
}
/**
* Checks whether the project class loader has changed, and if so
* unregisters any view rules that use classes from the old class loader. It
* then returns the class loader to be used.
*/
private ClassLoader updateClassLoader() {
ClassLoader classLoader = mRuleLoader.getClassLoader();
if (mUserClassLoader != null && classLoader != mUserClassLoader) {
// We have to unload all the IViewRules from the old class
List<Object> dispose = new ArrayList<Object>();
for (Map.Entry<Object, IViewRule> entry : mRulesCache.entrySet()) {
IViewRule rule = entry.getValue();
if (rule.getClass().getClassLoader() == mUserClassLoader) {
dispose.add(entry.getKey());
}
}
for (Object object : dispose) {
mRulesCache.remove(object);
}
}
mUserClassLoader = classLoader;
return mUserClassLoader;
}
/**
* Load a rule using its descriptor. This will try to first load the rule using its
* actual FQCN and if that fails will find the first parent that works in the view
* hierarchy.
*/
private IViewRule loadRule(UiViewElementNode element) {
if (element == null) {
return null;
}
String targetFqcn = null;
ViewElementDescriptor targetDesc = null;
ElementDescriptor d = element.getDescriptor();
if (d instanceof ViewElementDescriptor) {
targetDesc = (ViewElementDescriptor) d;
}
if (d == null || !(d instanceof ViewElementDescriptor)) {
// This should not happen. All views should have some kind of *view* element
// descriptor. Maybe the project is not complete and doesn't build or something.
// In this case, we'll use the descriptor of the base android View class.
targetDesc = getBaseViewDescriptor();
}
// Check whether any of the custom view .jar files have changed and if so
// unregister previously cached view rules to force a new view rule to be loaded.
updateClassLoader();
// Return the rule if we find it in the cache, even if it was stored as null
// (which means we didn't find it earlier, so don't look for it again)
IViewRule rule = mRulesCache.get(targetDesc);
if (rule != null || mRulesCache.containsKey(targetDesc)) {
return rule;
}
// Get the descriptor and loop through the super class hierarchy
for (ViewElementDescriptor desc = targetDesc;
desc != null;
desc = desc.getSuperClassDesc()) {
// Get the FQCN of this View
String fqcn = desc.getFullClassName();
if (fqcn == null) {
// Shouldn't be happening.
return null;
}
// The first time we keep the FQCN around as it's the target class we were
// initially trying to load. After, as we move through the hierarchy, the
// target FQCN remains constant.
if (targetFqcn == null) {
targetFqcn = fqcn;
}
if (fqcn.indexOf('.') == -1) {
// Deal with unknown descriptors; these lack the full qualified path and
// elements in the layout without a package are taken to be in the
// android.widget package.
fqcn = ANDROID_WIDGET_PREFIX + fqcn;
}
// Try to find a rule matching the "real" FQCN. If we find it, we're done.
// If not, the for loop will move to the parent descriptor.
rule = loadRule(fqcn, targetFqcn);
if (rule != null) {
// We found one.
// As a side effect, loadRule() also cached the rule using the target FQCN.
return rule;
}
}
// Memorize in the cache that we couldn't find a rule for this descriptor
mRulesCache.put(targetDesc, null);
return null;
}
/**
* Try to load a rule given a specific FQCN. This looks for an exact match in either
* the ADT scripts or the project scripts and does not look at parent hierarchy.
* <p/>
* Once a rule is found (or not), it is stored in a cache using its target FQCN
* so we don't try to reload it.
* <p/>
* The real FQCN is the actual rule class we're loading, e.g. "android.view.View"
* where target FQCN is the class we were initially looking for, which might be the same as
* the real FQCN or might be a derived class, e.g. "android.widget.TextView".
*
* @param realFqcn The FQCN of the rule class actually being loaded.
* @param targetFqcn The FQCN of the class actually processed, which might be different from
* the FQCN of the rule being loaded.
*/
IViewRule loadRule(String realFqcn, String targetFqcn) {
if (realFqcn == null || targetFqcn == null) {
return null;
}
// Return the rule if we find it in the cache, even if it was stored as null
// (which means we didn't find it earlier, so don't look for it again)
IViewRule rule = mRulesCache.get(realFqcn);
if (rule != null || mRulesCache.containsKey(realFqcn)) {
return rule;
}
// Look for class via reflection
try {
// For now, we package view rules for the builtin Android views and
// widgets with the tool in a special package, so look there rather
// than in the same package as the widgets.
String ruleClassName;
ClassLoader classLoader;
if (realFqcn.startsWith("android.") || //$NON-NLS-1$
realFqcn.equals(VIEW_MERGE) ||
realFqcn.endsWith(".GridLayout") || //$NON-NLS-1$ // Temporary special case
// FIXME: Remove this special case as soon as we pull
// the MapViewRule out of this code base and bundle it
// with the add ons
realFqcn.startsWith("com.google.android.maps.")) { //$NON-NLS-1$
// This doesn't handle a case where there are name conflicts
// (e.g. where there are multiple different views with the same
// class name and only differing in package names, but that's a
// really bad practice in the first place, and if that situation
// should come up in the API we can enhance this algorithm.
String packageName = ViewRule.class.getName();
packageName = packageName.substring(0, packageName.lastIndexOf('.'));
classLoader = RulesEngine.class.getClassLoader();
int dotIndex = realFqcn.lastIndexOf('.');
String baseName = realFqcn.substring(dotIndex+1);
// Capitalize rule class name to match naming conventions, if necessary (<merge>)
if (Character.isLowerCase(baseName.charAt(0))) {
if (baseName.equals(VIEW_TAG)) {
// Hack: ViewRule is generic for the "View" class, so we can't use it
// for the special XML "view" tag (lowercase); instead, the rule is
// named "ViewTagRule" instead.
baseName = "ViewTag"; //$NON-NLS-1$
}
baseName = Character.toUpperCase(baseName.charAt(0)) + baseName.substring(1);
}
ruleClassName = packageName + "." + //$NON-NLS-1$
baseName + "Rule"; //$NON-NLS-1$
} else {
// Initialize the user-classpath for 3rd party IViewRules, if necessary
classLoader = updateClassLoader();
if (classLoader == null) {
// The mUserClassLoader can be null; this is the typical scenario,
// when the user is only using builtin layout rules.
// This means however we can't resolve this fqcn since it's not
// in the name space of the builtin rules.
mRulesCache.put(realFqcn, null);
return null;
}
// For other (3rd party) widgets, look in the same package (though most
// likely not in the same jar!)
ruleClassName = realFqcn + "Rule"; //$NON-NLS-1$
}
Class<?> clz = Class.forName(ruleClassName, true, classLoader);
rule = (IViewRule) clz.newInstance();
return initializeRule(rule, targetFqcn);
} catch (ClassNotFoundException ex) {
// Not an unexpected error - this means that there isn't a helper for this
// class.
} catch (InstantiationException e) {
// This is NOT an expected error: fail.
AdtPlugin.log(e, "load rule error (%s): %s", realFqcn, e.toString());
} catch (IllegalAccessException e) {
// This is NOT an expected error: fail.
AdtPlugin.log(e, "load rule error (%s): %s", realFqcn, e.toString());
}
// Memorize in the cache that we couldn't find a rule for this real FQCN
mRulesCache.put(realFqcn, null);
return null;
}
/**
* Initialize a rule we just loaded. The rule has a chance to examine the target FQCN
* and bail out.
* <p/>
* Contract: the rule is not in the {@link #mRulesCache} yet and this method will
* cache it using the target FQCN if the rule is accepted.
* <p/>
* The real FQCN is the actual rule class we're loading, e.g. "android.view.View"
* where target FQCN is the class we were initially looking for, which might be the same as
* the real FQCN or might be a derived class, e.g. "android.widget.TextView".
*
* @param rule A rule freshly loaded.
* @param targetFqcn The FQCN of the class actually processed, which might be different from
* the FQCN of the rule being loaded.
* @return The rule if accepted, or null if the rule can't handle that FQCN.
*/
private IViewRule initializeRule(IViewRule rule, String targetFqcn) {
try {
if (rule.onInitialize(targetFqcn, new ClientRulesEngine(this, targetFqcn))) {
// Add it to the cache and return it
mRulesCache.put(targetFqcn, rule);
return rule;
} else {
rule.onDispose();
}
} catch (Exception e) {
AdtPlugin.log(e, "%s.onInit() failed: %s",
rule.getClass().getSimpleName(),
e.toString());
}
return null;
}
}