|
|
@@ -0,0 +1,516 @@
|
|
|
+package net.ranides.assira.reflection.impl.scanner;
|
|
|
+
|
|
|
+import net.ranides.assira.collection.arrays.NativeArray;
|
|
|
+import net.ranides.assira.functional.special.LazyFunction;
|
|
|
+import net.ranides.assira.generic.LazyReference;
|
|
|
+import net.ranides.assira.generic.ValueUtils;
|
|
|
+import net.ranides.assira.reflection.IAttribute;
|
|
|
+import net.ranides.assira.reflection.IClass;
|
|
|
+import net.ranides.assira.reflection.IElement;
|
|
|
+import net.ranides.assira.reflection.IField;
|
|
|
+import net.ranides.assira.reflection.ObjectScanner;
|
|
|
+
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.IdentityHashMap;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Optional;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+public class ScanWalker {
|
|
|
+
|
|
|
+ private static final String TYPE_PATH = "/";
|
|
|
+
|
|
|
+ private static final String NAME_PATH = "/";
|
|
|
+
|
|
|
+ private static final String ID_PATH = ".";
|
|
|
+
|
|
|
+ private final Map<Object, Long> visited = new IdentityHashMap<>();
|
|
|
+
|
|
|
+ private long counter = 0;
|
|
|
+
|
|
|
+ private final Object root;
|
|
|
+
|
|
|
+ private final ObjectScanner.ObjectListener listener;
|
|
|
+
|
|
|
+ public ScanWalker(Object root, ObjectScanner.ObjectListener listener) {
|
|
|
+ this.root = root;
|
|
|
+ this.listener = listener;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void walk() {
|
|
|
+ context(null, null, IClass.OBJECT, root).accept(listener);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void mark(ObjectScanner.ObjectContext ctx) {
|
|
|
+ if(!ctx.isPrimitive()) {
|
|
|
+ visited.put(ctx.value(), ctx.id());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private ObjectScanner.ObjectContext context(ObjectScanner.ObjectContext parent, IField field) {
|
|
|
+ Object value = ScanSupport.UNDEFINED;
|
|
|
+ if(!parent.isNull()) {
|
|
|
+ try {
|
|
|
+ value = field.accessible().get(parent.value());
|
|
|
+ } catch (Exception cause) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ }
|
|
|
+ IClass<?> declared = field.type();
|
|
|
+ IClass<?> actual = IClass.typefor(value);
|
|
|
+
|
|
|
+ if(ScanSupport.isList(declared, actual)) {
|
|
|
+ return new CList(parent, field, value);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isCollection(declared, actual)) {
|
|
|
+ return new CCollection(parent, field, value);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isMap(declared, actual)) {
|
|
|
+ return new CMap(parent, field, value);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isArray(declared, actual)) {
|
|
|
+ return new CArray(parent, field, value);
|
|
|
+ }
|
|
|
+ return new CObject(parent, field, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private ObjectScanner.ObjectContext context(ObjectScanner.ObjectContext parent, String name, IClass<?> declared, Object value) {
|
|
|
+ IClass<?> actual = IClass.typefor(value);
|
|
|
+
|
|
|
+ if(ScanSupport.isList(declared, actual)) {
|
|
|
+ return new CList(parent, name, declared, value);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isCollection(declared, actual)) {
|
|
|
+ return new CCollection(parent, name, declared, value);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isMap(declared, actual)) {
|
|
|
+ return new CMap(parent, name, declared, value);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isArray(declared, actual)) {
|
|
|
+ return new CArray(parent, name, declared, value);
|
|
|
+ }
|
|
|
+ return new CObject(parent, name, declared, value);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private abstract class CAbstract implements ObjectScanner.ObjectContext {
|
|
|
+ private final ObjectScanner.ObjectContext parent;
|
|
|
+ private final String name;
|
|
|
+ private final IClass<?> declared;
|
|
|
+ private final IClass<?> actual;
|
|
|
+ private final Object value;
|
|
|
+ private final long id;
|
|
|
+
|
|
|
+ private final Optional<IField> field;
|
|
|
+
|
|
|
+ private final LazyFunction<String> typePath = LazyReference.concurrent();
|
|
|
+ private final LazyFunction<String> namePath = LazyReference.concurrent();
|
|
|
+ private final LazyFunction<String> idPath = LazyReference.concurrent();
|
|
|
+
|
|
|
+ public CAbstract(ObjectScanner.ObjectContext parent, IField field, Object value) {
|
|
|
+ this.field = Optional.of(field);
|
|
|
+ this.parent = parent;
|
|
|
+ this.name = field.name();
|
|
|
+ this.declared = field.type();
|
|
|
+ this.value = value;
|
|
|
+ this.actual = IClass.typefor(value);
|
|
|
+ this.id = counter++;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public CAbstract(ObjectScanner.ObjectContext parent, String name, IClass<?> declared, Object value) {
|
|
|
+ this.field = Optional.empty();
|
|
|
+ this.parent = parent;
|
|
|
+ this.name = name;
|
|
|
+ this.declared = declared;
|
|
|
+ this.value = value;
|
|
|
+ this.actual = IClass.typefor(value);
|
|
|
+ this.id = counter++;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ObjectScanner.ObjectContext parent() {
|
|
|
+ return parent;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String typeName() {
|
|
|
+ return declared.shortname();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String name() {
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IClass<?> type() {
|
|
|
+ return declared;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IClass<?> actual() {
|
|
|
+ return actual;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object value() {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public long id() {
|
|
|
+ return id;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public long ref() {
|
|
|
+ return ValueUtils.or(visited.get(value), id);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String typePath() {
|
|
|
+ return typePath.apply(() -> isRoot() ? typeName() : parent.typePath() + TYPE_PATH + typeName());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String namePath() {
|
|
|
+ return namePath.apply(() -> {
|
|
|
+ if(parent != null) {
|
|
|
+ String head = parent.namePath();
|
|
|
+ if(head != null) {
|
|
|
+ return head + NAME_PATH + name();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return name();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String idPath() {
|
|
|
+ return idPath.apply(() -> isRoot() ? String.valueOf(id()) : parent.idPath() + ID_PATH + id());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isRoot() {
|
|
|
+ return value == root || parent == null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isField() {
|
|
|
+ return field.isPresent();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isVisited() {
|
|
|
+ return visited.containsKey(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isDefined() {
|
|
|
+ return value == ScanSupport.UNDEFINED;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isPrimitive() {
|
|
|
+ return declared.isPrimitive();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isNull() {
|
|
|
+ return value == null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isSubclass() {
|
|
|
+ return declared.isInstance(value) && !declared.equals(actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private class CObject extends CAbstract implements ObjectScanner.ObjectContext {
|
|
|
+ public CObject(ObjectScanner.ObjectContext parent, IField field, Object value) {
|
|
|
+ super(parent, field, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public CObject(ObjectScanner.ObjectContext parent, String name, IClass<?> declared, Object value) {
|
|
|
+ super(parent, name, declared, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void accept(ObjectScanner.ObjectListener listener) {
|
|
|
+ try {
|
|
|
+ if (!listener.enterObject(this)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isNull() && !isPrimitive()) {
|
|
|
+ actual()
|
|
|
+ .fields()
|
|
|
+ .require(IAttribute.ANY)
|
|
|
+ .discard(IAttribute.STATIC)
|
|
|
+ .stream()
|
|
|
+ .sort(Comparator.comparing(IElement::name))
|
|
|
+ .map(field -> context(this, field))
|
|
|
+ .each(context -> context.accept(listener));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ mark(this);
|
|
|
+ listener.leaveObject(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private class CArray extends CAbstract implements ObjectScanner.ArrayContext {
|
|
|
+
|
|
|
+ private final NativeArray array;
|
|
|
+
|
|
|
+ public CArray(ObjectScanner.ObjectContext parent, IField field, Object value) {
|
|
|
+ super(parent, field, value);
|
|
|
+ this.array = NativeArray.wrap(value());
|
|
|
+ }
|
|
|
+
|
|
|
+ public CArray(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
|
|
|
+ super(parent, name, type, value);
|
|
|
+ this.array = NativeArray.wrap(value());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IClass<?> component() {
|
|
|
+ return type().component();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int size() {
|
|
|
+ return array.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object at(int index) {
|
|
|
+ return array.get(index);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void accept(ObjectScanner.ObjectListener listener) {
|
|
|
+ try {
|
|
|
+ if (!listener.enterArray(this)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!isNull()) {
|
|
|
+ IClass<?> cmp = component();
|
|
|
+ for (int index = 0, n = size(); index < n; index++) {
|
|
|
+ context(this, "[" + index + "]", cmp, at(index)).accept(listener);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ mark(this);
|
|
|
+ listener.leaveArray(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class CList extends CAbstract implements ObjectScanner.ListContext {
|
|
|
+
|
|
|
+ private final List<?> list;
|
|
|
+ private final LazyFunction<IClass<?>> component = LazyReference.concurrent();
|
|
|
+
|
|
|
+ public CList(ObjectScanner.ObjectContext parent, IField field, Object value) {
|
|
|
+ super(parent, field, value);
|
|
|
+ this.list = (List<?>)value();
|
|
|
+ }
|
|
|
+
|
|
|
+ public CList(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
|
|
|
+ super(parent, name, type, value);
|
|
|
+ this.list = (List<?>)value();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String typeName() {
|
|
|
+ return type().shortname() + "<" + component().shortname() + ">";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IClass<?> component() {
|
|
|
+ return component.apply(() -> {
|
|
|
+ if(ScanSupport.isList(type())) {
|
|
|
+ return type().params().get(0);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isList(actual())) {
|
|
|
+ return actual().params().get(0);
|
|
|
+ }
|
|
|
+ return IClass.OBJECT;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int size() {
|
|
|
+ return list.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object at(int index) {
|
|
|
+ return list.get(index);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void accept(ObjectScanner.ObjectListener listener) {
|
|
|
+ try {
|
|
|
+ if (!listener.enterList(this)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!isNull()) {
|
|
|
+ IClass<?> cmp = component();
|
|
|
+ for (int index = 0, n = size(); index < n; index++) {
|
|
|
+ context(this, "[" + index + "]", cmp, at(index)).accept(listener);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ mark(this);
|
|
|
+ listener.leaveList(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class CCollection extends CAbstract implements ObjectScanner.CollectionContext {
|
|
|
+
|
|
|
+ private final Collection<?> collection;
|
|
|
+ private final LazyFunction<IClass<?>> component = LazyReference.concurrent();
|
|
|
+
|
|
|
+ public CCollection(ObjectScanner.ObjectContext parent, IField field, Object value) {
|
|
|
+ super(parent, field, value);
|
|
|
+ this.collection = (Collection<?>)value();
|
|
|
+ }
|
|
|
+
|
|
|
+ public CCollection(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
|
|
|
+ super(parent, name, type, value);
|
|
|
+ this.collection = (Collection<?>)value();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String typeName() {
|
|
|
+ return type().shortname() + "<" + component().shortname() + ">";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IClass<?> component() {
|
|
|
+ return component.apply(() -> {
|
|
|
+ if(ScanSupport.isCollection(type())) {
|
|
|
+ return type().params().get(0);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isCollection(actual())) {
|
|
|
+ return actual().params().get(0);
|
|
|
+ }
|
|
|
+ return IClass.OBJECT;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int size() {
|
|
|
+ return collection.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Iterator<?> iterator() {
|
|
|
+ return collection.iterator();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void accept(ObjectScanner.ObjectListener listener) {
|
|
|
+ try {
|
|
|
+ if (!listener.enterCollection(this)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!isNull()) {
|
|
|
+ IClass<?> cmp = component();
|
|
|
+ for (Object item : collection) {
|
|
|
+ context(this, "[?]", cmp, item).accept(listener);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ mark(this);
|
|
|
+ listener.leaveCollection(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class CMap extends CAbstract implements ObjectScanner.MapContext {
|
|
|
+
|
|
|
+ private final Map<?,?> map;
|
|
|
+ private final LazyFunction<IClass<?>> keyComponent = LazyReference.concurrent();
|
|
|
+ private final LazyFunction<IClass<?>> valueComponent = LazyReference.concurrent();
|
|
|
+
|
|
|
+ public CMap(ObjectScanner.ObjectContext parent, IField field, Object value) {
|
|
|
+ super(parent, field, value);
|
|
|
+ this.map = (Map<?,?>)value();
|
|
|
+ }
|
|
|
+
|
|
|
+ public CMap(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
|
|
|
+ super(parent, name, type, value);
|
|
|
+ this.map = (Map<?,?>)value();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int size() {
|
|
|
+ return map.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String typeName() {
|
|
|
+ return type().shortname() + "<" + keyComponent().shortname() + "," + valueComponent().shortname() + ">";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IClass<?> keyComponent() {
|
|
|
+ return keyComponent.apply(() -> {
|
|
|
+ if(ScanSupport.isMap(type())) {
|
|
|
+ return type().params().get(0);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isMap(actual())) {
|
|
|
+ return actual().params().get(0);
|
|
|
+ }
|
|
|
+ return IClass.OBJECT;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IClass<?> valueComponent() {
|
|
|
+ return valueComponent.apply(() -> {
|
|
|
+ if(ScanSupport.isMap(type())) {
|
|
|
+ return type().params().get(1);
|
|
|
+ }
|
|
|
+ if(ScanSupport.isMap(actual())) {
|
|
|
+ return actual().params().get(1);
|
|
|
+ }
|
|
|
+ return IClass.OBJECT;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<? extends Map.Entry<?, ?>> entries() {
|
|
|
+ return map.entrySet();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void accept(ObjectScanner.ObjectListener listener) {
|
|
|
+ try {
|
|
|
+ if (!listener.enterMap(this)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!isNull()) {
|
|
|
+ IClass<?> cmp = valueComponent();
|
|
|
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
|
|
|
+ context(this, "[" + entry.getKey() + "]", cmp, entry.getValue()).accept(listener);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ mark(this);
|
|
|
+ listener.leaveMap(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|