/** * Copyright (C) 2006-2017 INRIA and contributors * Spoon - http://spoon.gforge.inria.fr/ * * This software is governed by the CeCILL-C License under French law and * abiding by the rules of distribution of free software. You can use, modify * and/or redistribute the software under the terms of the CeCILL-C license as * circulated by CEA, CNRS and INRIA at http://www.cecill.info. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ package spoon.reflect.visitor; import spoon.reflect.declaration.CtElement; import spoon.reflect.visitor.chain.CtScannerListener; import spoon.reflect.visitor.chain.ScanningMode; import java.util.ArrayList; import java.util.Collection; import java.util.Map; /** * Extends {@link CtScanner}, to support early termination of scanning process and scan listeners. * It is useful when your algorithm is searching for a specific node only. * In this case, you can call {@link #terminate()}, which ensures that no more AST nodes are visited, *<br> * It is possible to register an implementation of {@link CtScannerListener}, * whose {@link CtScannerListener#enter(CtElement)}/{@link CtScannerListener#exit(CtElement)} * methods are called before/after each AST node is visited.<br> * * @param <T> the type of the result produced by this scanner. */ public class EarlyTerminatingScanner<T> extends CtScanner { private boolean terminate = false; private T result; private CtScannerListener listener; protected void terminate() { terminate = true; } protected boolean isTerminated() { return terminate; } protected void setResult(T result) { this.result = result; } /** * @return the result of scanning - the value, which was stored by a previous call of {@link #setResult(Object)} */ public T getResult() { return result; } /** * @return null or the implementation of {@link CtScannerListener}, which is registered to listen for enter/exit of nodes during scanning of the AST */ public CtScannerListener getListener() { return listener; } /** * @param listener the implementation of {@link CtScannerListener}, which will be called back when entering/exiting * odes during scanning. * @return this to support fluent API */ public EarlyTerminatingScanner<T> setListener(CtScannerListener listener) { this.listener = listener; return this; } @Override public void scan(Collection<? extends CtElement> elements) { if (isTerminated() || elements == null) { return; } // we use defensive copy so as to be able to change the class while scanning // otherwise one gets a ConcurrentModificationException for (CtElement e : new ArrayList<>(elements)) { scan(e); if (isTerminated()) { return; } } } @Override public void scan(CtElement element) { if (element == null || isTerminated()) { return; } if (listener == null) { //the listener is not defined //visit this element and may be children doScan(element, ScanningMode.NORMAL); } else { //the listener is defined, call it's enter method first ScanningMode mode = listener.enter(element); if (mode != ScanningMode.SKIP_ALL) { //the listener decided to visit this element and may be children doScan(element, mode); //then call exit, only if enter returned true listener.exit(element); } //else the listener decided to skip this element and all children. Do not call exit. } } /** * This method is called ONLY when the listener decides that the current element and children should be visited. * Subclasses can override it to react accordingly. */ protected void doScan(CtElement element, ScanningMode mode) { super.scan(element); } @Override public void scan(Object o) { if (isTerminated() || o == null) { return; } if (o instanceof CtElement) { scan((CtElement) o); } else if (o instanceof Collection<?>) { scan((Collection<? extends CtElement>) o); } else if (o instanceof Map<?, ?>) { for (Object obj : ((Map) o).values()) { scan(obj); if (isTerminated()) { return; } } } } }