/* * Copyright 2017 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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.thoughtworks.go.config; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.thoughtworks.go.config.preprocessor.ClassAttributeCache; /** * @understands visits all the nodes in the cruise config once using Java reflection */ public class GoConfigGraphWalker { private final Validatable rootValidatable; private final ClassAttributeCache.FieldCache fieldCache = new ClassAttributeCache.FieldCache(); private final ClassAttributeCache.AssignableCache canAssignToValidatableCache = new ClassAttributeCache.AssignableCache(); private final ClassAttributeCache.AssignableCache canAssignToCollectionCache = new ClassAttributeCache.AssignableCache(); public static class WalkedObject { private final Object obj; public WalkedObject(Object obj) { this.obj = obj; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } WalkedObject that = (WalkedObject) o; return !(obj != null ? obj != that.obj : that.obj != null); } @Override public int hashCode() { return obj != null ? obj.hashCode() : 0; } public boolean shouldWalk() { return obj != null && obj.getClass().getName().startsWith("com.thoughtworks"); } } public GoConfigGraphWalker(Validatable rootValidatable) { this.rootValidatable = rootValidatable; } public void walk(Handler handler) { walkSubtree(this.rootValidatable, new ConfigSaveValidationContext(null), handler); } private void walkSubtree(Object current, ConfigSaveValidationContext context, Handler handler) { WalkedObject walkedObject = new WalkedObject(current); if (!walkedObject.shouldWalk()) { return; } if (canAssignToValidatableCache.valuesFor(new AbstractMap.SimpleEntry<>(Validatable.class, current.getClass()))) { Validatable validatable = (Validatable) current; handler.handle(validatable, context); context = context.withParent(validatable); } walkCollection(current, context, handler); walkFields(current, context, handler); } private void walkFields(Object current, ConfigSaveValidationContext ctx, Handler handler) { for (Field field : getAllFields(current.getClass())) { field.setAccessible(true); try { Object o = field.get(current); if (o == null || isAConstantField(field) || field.isAnnotationPresent(IgnoreTraversal.class)) { continue; } walkSubtree(o, ctx, handler); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } private List<Field> getAllFields(Class klass) {//TODO: if not com.thoughtworks.go don't bother return new ArrayList<>(fieldCache.valuesFor(klass)); } private boolean isAConstantField(Field field) { int modifiers = field.getModifiers(); //MCCXL cannot assign value to final fields as it always uses the default constructor. Hence this assumption is OK return Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers); } private void walkCollection(Object current, ConfigSaveValidationContext ctx, Handler handler) { // We can only expect java to honor the contract of datastructure interfaces(read: List), // and not depend on how they choose to implement it, so we short-circuit at a level that we know will continue to work(bad, but safe) // TODO: do java.util.Map when needed, not handled yet, but its a simple EntrySet walk if (canAssignToCollectionCache.valuesFor(new AbstractMap.SimpleEntry<>(Collection.class, current.getClass()))) { Collection collection = (Collection) current; for (Object collectionItem : collection) { walkSubtree(collectionItem, ctx, handler); } } } public static interface Handler { void handle(Validatable validatable, ValidationContext ctx); } }