optimize: code generation

This commit is contained in:
hyb1996
2017-12-07 22:13:58 +08:00
parent 6ea30c6fcc
commit 8db26f5b11
24 changed files with 687 additions and 177 deletions

View File

@@ -0,0 +1,193 @@
package com.stardust.autojs.codegeneration;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import com.stardust.automator.UiGlobalSelector;
import com.stardust.automator.UiObject;
import com.stardust.view.accessibility.NodeInfo;
/**
* Created by Stardust on 2017/12/7.
*/
public class CodeGenerator {
public static final int UNTIL_FIND = 0;
public static final int FIND_ONE = 1;
public static final int WAIT_FOR = 2;
public static final int EXISTS = 3;
private ReadOnlyUiObject mRoot;
private ReadOnlyUiObject mTarget;
private boolean mUsingId = true;
private boolean mUsingDesc = true;
private boolean mUsingText = true;
private int mSearchMode = FIND_ONE;
private int mAction = -1;
public CodeGenerator(NodeInfo root, NodeInfo target) {
this(new ReadOnlyUiObject(root), new ReadOnlyUiObject(target));
}
public CodeGenerator(ReadOnlyUiObject root, ReadOnlyUiObject target) {
mRoot = root;
mTarget = target;
}
public void setUsingId(boolean usingId) {
mUsingId = usingId;
}
public void setUsingDesc(boolean usingDesc) {
mUsingDesc = usingDesc;
}
public void setUsingText(boolean usingText) {
mUsingText = usingText;
}
public void setSearchMode(int searchMode) {
mSearchMode = searchMode;
}
public void setAction(int action) {
mAction = action;
}
public String generateCode() {
UiObject collection = getCollectionParent(mTarget);
if (collection != null) {
return generateCodeForCollectionChild(collection, mTarget);
}
UiSelectorGenerator generator = new UiSelectorGenerator(mRoot, mTarget);
generator.setSearchMode(mSearchMode);
generator.setUsingDesc(mUsingDesc);
generator.setUsingId(mUsingId);
generator.setUsingText(mUsingText);
String selector = generateCode(generator, mRoot, mTarget, 2, 2);
if (selector == null)
return null;
return generateAction(selector);
}
protected String generateCode(UiSelectorGenerator generator, UiObject root, UiObject target, int maxParentLevel, int maxChildrenLevel) {
String selector = generator.generateSelectorCode();
if (selector != null) {
return selector;
}
if (maxChildrenLevel > 0) {
for (int i = 0; i < target.childCount(); i++) {
UiObject child = target.child(i);
if (child == null)
continue;
String childCode = generateCode(root, child, 0, maxChildrenLevel - 1);
if (childCode != null) {
return childCode + ".parent()";
}
}
}
if (maxParentLevel > 0 && target.parent() != null) {
int index = target.indexInParent();
if (index > 0) {
String parentCode = generateCode(root, target.parent(), maxParentLevel - 1, 0);
if (parentCode != null) {
return parentCode + "child(" + index + ")";
}
}
}
return null;
}
protected String generateCode(UiObject root, UiObject target, int maxParentLevel, int maxChildrenLevel) {
UiSelectorGenerator generator = new UiSelectorGenerator(root, target);
generator.setUsingId(mUsingId);
return generateCode(generator, root, target, maxParentLevel, maxChildrenLevel);
}
private String generateAction(String selector) {
if (selector == null)
return null;
if (mSearchMode == WAIT_FOR) {
return selector + ".waitFor()";
}
if (mSearchMode == EXISTS) {
return "if(" + selector + ".exists()){\n \n}";
}
String action = getAction();
if (action.isEmpty()) {
return selector;
} else {
return selector + "." + action;
}
}
private String getAction() {
switch (mAction) {
case AccessibilityNodeInfoCompat.ACTION_CLICK:
return "click()";
case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
return "longClick()";
case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
return "scrollBackward()";
case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
return "scrollForward()";
case AccessibilityNodeInfoCompat.ACTION_SET_TEXT:
return "setText(\"\")";
}
return "";
}
private String generateCodeForCollectionChild(UiObject collection, UiObject target) {
UiObject parent = target.parent();
if (parent == null)
return null;
UiObject collectionItem = null;
for (int i = 0; i < collection.childCount(); i++) {
if (inherits(collection.child(i), target)) {
collectionItem = collection.child(i);
break;
}
}
if (collectionItem == null)
return null;
String collectionCode = generateCode(mRoot, collection, 2, 0);
if (collectionCode == null)
return null;
String itemCode = generateCode(collectionItem, target, 1, 2);
if (itemCode == null)
return null;
return collectionCode + ".findOne().children().forEach(child => {\n"
+ "var target = child.findOne(" + itemCode + ");\n"
+ "target." + getAction() + ";\n"
+ "});";
}
private boolean inherits(UiObject root, UiObject target) {
for (int i = 0; i < root.childCount(); i++) {
UiObject child = root.child(i);
if (child != null) {
if (child.equals(target) || inherits(child, target)) {
return true;
}
}
}
return false;
}
private UiObject getCollectionParent(UiObject target) {
UiObject parent = target.parent();
while (parent != null) {
if (parent.rowCount() > 0 || parent.columnCount() > 0) {
return parent;
}
parent = parent.parent();
}
return null;
}
}

View File

@@ -6,7 +6,6 @@ import android.support.annotation.Nullable;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import com.stardust.automator.UiObject;
import com.stardust.automator.UiObjectCollection;
import com.stardust.view.accessibility.NodeInfo;
/**
@@ -16,19 +15,27 @@ import com.stardust.view.accessibility.NodeInfo;
public class ReadOnlyUiObject extends UiObject {
private NodeInfo mNodeInfo;
private boolean mUsingId;
private boolean mUsingText;
private boolean mUsingDesc;
public ReadOnlyUiObject(NodeInfo info) {
super(info, info.depth);
super(info, info.depth, -1);
mNodeInfo = info;
}
public ReadOnlyUiObject(NodeInfo info, int indexInParent) {
super(info, info.depth, indexInParent);
mNodeInfo = info;
}
@Nullable
@Override
public UiObject child(int i) {
return new ReadOnlyUiObject(mNodeInfo.getChildren().get(i));
return new ReadOnlyUiObject(mNodeInfo.getChildren().get(i), i);
}
@Nullable
@Override
public UiObject parent() {
return mNodeInfo.parent == null ? null : new ReadOnlyUiObject(mNodeInfo.parent);
}
@Override
@@ -189,6 +196,82 @@ public class ReadOnlyUiObject extends UiObject {
return mNodeInfo.scrollable;
}
@Override
public boolean isCheckable() {
return checkable();
}
@Override
public boolean isChecked() {
return checked();
}
@Override
public boolean isFocusable() {
return focusable();
}
@Override
public boolean isFocused() {
return focused();
}
@Override
public boolean isVisibleToUser() {
return visibleToUser();
}
@Override
public boolean isAccessibilityFocused() {
return accessibilityFocused();
}
@Override
public boolean isSelected() {
return selected();
}
@Override
public boolean isClickable() {
return clickable();
}
@Override
public boolean isLongClickable() {
return longClickable();
}
@Override
public boolean isEnabled() {
return enabled();
}
@Override
public boolean isPassword() {
return password();
}
@Override
public boolean isScrollable() {
return scrollable();
}
@Override
public boolean isContextClickable() {
return mNodeInfo.contextClickable;
}
@Override
public boolean isDismissable() {
return mNodeInfo.dismissable;
}
@Override
public boolean isEditable() {
return mNodeInfo.editable;
}
@Override
public int row() {
return mNodeInfo.row;
@@ -223,4 +306,27 @@ public class ReadOnlyUiObject extends UiObject {
public void recycle() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ReadOnlyUiObject that = (ReadOnlyUiObject) o;
return mNodeInfo.equals(that.mNodeInfo);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + mNodeInfo.hashCode();
return result;
}
@Override
public String toString() {
return mNodeInfo.toString();
}
}

View File

@@ -1,49 +1,81 @@
package com.stardust.autojs.codegeneration;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import com.stardust.automator.UiGlobalSelector;
import com.stardust.automator.UiObject;
import com.stardust.util.Consumer;
import com.stardust.view.accessibility.NodeInfo;
import static com.stardust.autojs.codegeneration.CodeGenerator.FIND_ONE;
import static com.stardust.autojs.codegeneration.CodeGenerator.UNTIL_FIND;
/**
* Created by Stardust on 2017/8/4.
* Created by Stardust on 2017/12/7.
*/
public class UiSelectorGenerator {
public static final int UNTIL_FIND = 0;
public static final int FIND_ONE = 1;
public static final int WAIT_FOR = 2;
public static final int EXISTS = 3;
private ReadOnlyUiObject mRoot;
private ReadOnlyUiObject mTarget;
private UiObject mRoot;
private UiObject mTarget;
private boolean mUsingId = true;
private boolean mUsingDesc = true;
private boolean mUsingText = true;
private UiGlobalSelector mUiGlobalSelector;
private int mSearchMode = FIND_ONE;
private int mAction = -1;
public UiSelectorGenerator(NodeInfo root, NodeInfo target) {
this(new ReadOnlyUiObject(root), new ReadOnlyUiObject(target));
}
public UiSelectorGenerator(ReadOnlyUiObject root, ReadOnlyUiObject target) {
public UiSelectorGenerator(UiObject root, UiObject target) {
mRoot = root;
mTarget = target;
}
public void setSearchMode(int searchMode) {
mSearchMode = searchMode;
public UiGlobalSelector generateSelector() {
UiGlobalSelector selector = new UiGlobalSelector();
if (mUsingId &&
tryWithStringCondition(selector, mTarget.id(), selector::id)) {
return selector;
}
if (tryWithStringCondition(selector, mTarget.className(), selector::className)) {
return selector;
}
if (mUsingText &&
tryWithStringCondition(selector, mTarget.text(), selector::text)) {
return selector;
}
if (mUsingDesc &&
tryWithStringCondition(selector, mTarget.desc(), selector::desc)) {
return selector;
}
if (mTarget.scrollable() && tryWithBooleanCondition(selector, mTarget.scrollable(), selector::scrollable)) {
return selector;
}
if (mTarget.clickable() && tryWithBooleanCondition(selector, mTarget.clickable(), selector::clickable)) {
return selector;
}
if (mTarget.selected() && tryWithBooleanCondition(selector, mTarget.selected(), selector::selected)) {
return selector;
}
if (mTarget.checkable() && tryWithBooleanCondition(selector, mTarget.checkable(), selector::checkable)) {
return selector;
}
if (mTarget.checked() && tryWithBooleanCondition(selector, mTarget.checked(), selector::checked)) {
return selector;
}
if (mTarget.longClickable() && tryWithBooleanCondition(selector, mTarget.longClickable(), selector::longClickable)) {
return selector;
}
if (tryWithIntCondition(selector, mTarget.depth(), selector::depth)) {
return selector;
}
return null;
}
public void setAction(int action) {
mAction = action;
public String generateSelectorCode() {
UiGlobalSelector selector = generateSelector();
if (selector == null) {
return null;
}
return selector + (mSearchMode == FIND_ONE ? ".findOne()" : ".untilFind()");
}
public void setUsingId(boolean usingId) {
mUsingId = usingId;
}
@@ -56,94 +88,37 @@ public class UiSelectorGenerator {
mUsingText = usingText;
}
public void setSearchMode(int searchMode) {
mSearchMode = searchMode;
}
private boolean tryWithStringCondition(String name, String value, Consumer<String> condition, StringBuilder code) {
if (value == null || value.isEmpty())
private boolean tryWithBooleanCondition(UiGlobalSelector selector, boolean value, Consumer<Boolean> condition) {
condition.accept(value);
return shouldStopGeneration(selector);
}
private boolean tryWithStringCondition(UiGlobalSelector selector, String value, Consumer<String> condition) {
if (value == null || value.isEmpty()) {
return false;
code.append('.').append(name).append("(\"").append(value).append("\")");
}
condition.accept(value);
return isConditionEnough();
return shouldStopGeneration(selector);
}
private boolean tryWithIntCondition(String name, int value, Consumer<Integer> condition, StringBuilder code) {
code.append('.').append(name).append('(').append(value).append(')');
condition.accept(value);
return isConditionEnough();
}
private boolean isConditionEnough() {
private boolean shouldStopGeneration(UiGlobalSelector selector) {
if (mSearchMode == UNTIL_FIND) {
return !mUiGlobalSelector.findAndReturnList(mRoot).isEmpty();
return !selector.findAndReturnList(mRoot).isEmpty();
} else {
return mUiGlobalSelector.findAndReturnList(mRoot).size() == 1;
return selector.findAndReturnList(mRoot).size() == 1;
}
}
public String generate() {
String selector = generateSelector();
if (selector == null) {
return null;
}
//remove '.'
selector = selector.substring(1);
if (mSearchMode == WAIT_FOR) {
return selector + ".waitFor()";
}
if (mSearchMode == EXISTS) {
return "if(" + selector + ".exists()){\n \n}";
}
String action = getAction();
if (action == null) {
return selector;
} else {
return selector + action;
}
private boolean tryWithIntCondition(UiGlobalSelector selector, int value, Consumer<Integer> condition) {
condition.accept(value);
return shouldStopGeneration(selector);
}
private String getAction() {
switch (mAction) {
case AccessibilityNodeInfoCompat.ACTION_CLICK:
return ".click()";
case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
return ".longClick()";
case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
return ".scrollBackward()";
case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
return ".scrollForward()";
case AccessibilityNodeInfoCompat.ACTION_SET_TEXT:
return ".setText(\"\")";
}
return null;
}
private String generateSelector() {
mUiGlobalSelector = new UiGlobalSelector();
StringBuilder code = new StringBuilder();
if (mUsingId &&
tryWithStringCondition("id", mTarget.id(), mUiGlobalSelector::id, code)) {
return code.toString();
}
if (tryWithIntCondition("depth", mTarget.depth(), mUiGlobalSelector::depth, code)) {
return code.toString();
}
if (tryWithStringCondition("className", mTarget.className(), mUiGlobalSelector::className, code)) {
return code.toString();
}
if (mUsingText &&
tryWithStringCondition("text", mTarget.text(), mUiGlobalSelector::text, code)) {
return code.toString();
}
if (mUsingDesc &&
tryWithStringCondition("desc", mTarget.desc(), mUiGlobalSelector::desc, code)) {
return code.toString();
}
return null;
}
}