Change.java

/*
 * Copyright (C) 2007-2010 J��lio Vilmar Gesser.
 * Copyright (C) 2011, 2013-2023 The JavaParser Team.
 *
 * This file is part of JavaParser.
 *
 * JavaParser can be used either under the terms of
 * a) the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * b) the terms of the Apache License
 *
 * You should have received a copy of both licenses in LICENCE.LGPL and
 * LICENCE.APACHE. Please refer to those files for details.
 *
 * JavaParser 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
 * GNU Lesser General Public License for more details.
 */
package com.github.javaparser.printer.lexicalpreservation.changes;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.observer.ObservableProperty;
import com.github.javaparser.printer.concretesyntaxmodel.CsmConditional;
import com.github.javaparser.utils.Utils;

/**
 * This represents a change that has happened to a specific Node.
 */
public interface Change {

    default boolean evaluate(CsmConditional csmConditional, Node node) {
        switch(csmConditional.getCondition()) {
            case FLAG:
                return csmConditional.getProperties().stream().anyMatch(p -> (Boolean) getValue(p, node));
            case IS_NOT_EMPTY:
                return !Utils.valueIsNullOrEmpty(getValue(csmConditional.getProperty(), node));
            case IS_EMPTY:
                return Utils.valueIsNullOrEmpty(getValue(csmConditional.getProperty(), node));
            case IS_PRESENT:
                return !Utils.valueIsNullOrEmptyStringOrOptional(getValue(csmConditional.getProperty(), node))
                		&& !isEvaluatedOnDerivedProperty(csmConditional.getProperty());
            default:
                throw new UnsupportedOperationException("" + csmConditional.getProperty() + " " + csmConditional.getCondition());
        }
    }

	/*
	 * Evaluate on derived property.
	 *
	 * Currently the evaluation of the conditions is carried out in relation to the
	 * presence of value in the field/attribute of a class referenced by a property
	 * (for example the BODY property is referenced to the body field in the
	 * LambdaExpr class) but this is not quite correct.
	 *
	 * Indeed, there are attributes that are derived. The meaning of a derived
	 * attribute (annotated with the DerivedProperty annotation) is not very clear.
	 * Assuming that it is an existing attribute and accessible by another property,
	 * for example this is the case for the EXPRESSION_BODY property which allows
	 * access to a derived field (which is also accessible by the BODY property).
	 *
	 * The 2 properties EXPRESSION_BODY and BODY have a different meaning because
	 * one references a simple expression while the other references a list of
	 * expressions (this distinction is particularly interesting in the case of
	 * lambda expressions).
	 *
	 * In this particular case, the verification of the condition defined in the
	 * syntax model used by LPP must not succeed if the nature of the property is
	 * modified. So if we modify a lamba expression composed of a single expression
	 * by replacing it with a list of expressions, the evaluation of a condition
	 * relating to the presence of the EXPRESSION_BODY property, which makes it
	 * possible to determine the nature of the change, cannot not lead to a verified
	 * proposition which could be the case if we only consider that the field
	 * referenced by the EXPRESSION_BODY property has an acceptable value before the
	 * actual modification.
	 *
	 * This is why we also check if it is a derived property whose name coincides
	 * (*) with the updated property. If this is the case, we admit that the
	 * verification of the condition must fail so that we can execute the else
	 * clause of the condition. I'm not sure this issue #3949 is completely resolved
	 * by this change.
	 *
	 * (*) Assuming that by convention the derived property is suffixed with the
	 * name of the property it derives from (e.g.. EXPRESSION_BODY which matches an
	 * expression would derive from BODY which matches a list of expressions), we
	 * could deduce that EXPRESSION_BODY and BODY actually represent the same field
	 * but the validation condition must not be checked.
	 */
    default boolean isEvaluatedOnDerivedProperty(ObservableProperty property) {
    	ObservableProperty currentProperty = getProperty();
		/*
		 * By convention we admit that the derived property is suffixed with the name of
		 * the property it derives from (e.g. EXPRESSION_BODY which matches an
		 * expression would derive from BODY which matches a list of expressions), so we
		 * could deduce that EXPRESSION_BODY and BODY actually represent the same
		 * field but the validation condition must not be checked.
		 * Be careful because NoChange property must not affect this evaluation.
		 */
    	return currentProperty != null
    			&& (property.isDerived()
    					&& property.name().endsWith(currentProperty.name()));
    }

	/*
	 * Assuming that by convention the derived property is suffixed
	 * with the name of the property it derives from (e.g. EXPRESSION_BODY which
	 * matches an expression vs a list of expressions would derive from BODY) We
	 * could deduce that EXPRESSION_BODY and BODY actually represent the same
	 * property but the validation condition is not checked.
	 */
    ObservableProperty getProperty();

    Object getValue(ObservableProperty property, Node node);
}