// SPDX-FileCopyrightText: 2020 Roberto Posenato // // SPDX-License-Identifier: LGPL-3.0-or-later package it.univr.di.cstnu.graph; import javax.annotation.Nonnull; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.Serial; /** * Abstract component as a base class for nodes and edges. * * @author posenato * @version $Rev$ */ public abstract class AbstractComponent implements Component { /** * 2L: removed color attribute. */ @Serial private static final long serialVersionUID = 2L; /** * To provide a unique ID for the default creation of the component. */ static private int idSeq; /** * Possible name */ @Nonnull String name; /** * Since Java 9, Observable has no longer been supported. I decided to replace Observable using java.bean.PropertyChangeSupport allows the registration of listeners indexed by a key (string) that represents the property. Then, when a property is changed, it is enough to call * {@code pcs.firePropertyChange("theProperty", old, val);} *

* A listener 'l' of a property 'X' must be registered as {@code addObserver("X",l)}. *

* A listener must be implement {@link PropertyChangeListener}. */ PropertyChangeSupport pcs; /** * Both for nodes and edges, some algorithms can set this variable to true if the component is in a negative cycle. */ private boolean inNegativeCycle; /** * Once set, {@link #setName(String)} rejects any further direct call: the name is then managed by the * graph that owns this component, and must be changed only through {@code TemporalConstraintGraph.renameNode}/ * {@code renameEdge}. Set by every graph insertion path, including the fast ones that skip other checks. *

* This is a one-way lock, not an ownership counter: it is never cleared by removal or by a graph * {@code clear()}. A counter would need to be decremented on every removal path (including bulk * {@code clear()} and predecessor-graph reuse) and would still miss instances shared outside a * {@code DenseTCGraph}/{@code SparseTCGraph} (e.g., a {@code TCPredecessorGraph}), so it cannot safely detect * "no longer shared" and re-allow direct renaming; see {@code RENAME_AUDIT.md} §3.4. */ private boolean nameManagedByGraph; /** * Minimal constructor. The name will be 'c<id>'. */ protected AbstractComponent() { this(""); } /** * Creates a component with name {@code n} if not null, otherwise with a unique name "c<int>". * * @param n is the name of the component. */ protected AbstractComponent(String n) { name = ((n == null) || (n.isEmpty())) ? "c%d".formatted(idSeq++) : n; pcs = new PropertyChangeSupport(this); inNegativeCycle = false; } /** * Constructor to clone the component. * * @param c the component to clone. */ protected AbstractComponent(Component c) { pcs = new PropertyChangeSupport(this); if (c == null) { name = ""; return; } name = c.getName(); inNegativeCycle = c.inNegativeCycle(); } /** * An observer for the property. * * @param propertyName a {@link java.lang.String} object. * @param l a {@link java.beans.PropertyChangeListener} object. */ public final void addObserver(String propertyName, PropertyChangeListener l) { pcs.addPropertyChangeListener(propertyName, l); } /** * */ @Override public void clear() { inNegativeCycle = false; } /** * Implements the natural order of the component names. */ @Override public final int compareTo(@Nonnull Component o) { if (this == o) { return 0; } //don't change to compareTo, otherwise a lot of tests would fail return name.compareToIgnoreCase(o.getName()); } /** * Don't use this method to check if the component is equal to a value. *

* Use {@link #equalsByName(Component)} */ @Override @Deprecated public boolean equals(Object o) { return super.equals(o); } /** * @return true if the names are equal. */ @Override public final boolean equalsByName(Component c) { if (c == null) { return false; } return name.equals(c.getName()); } /** * */ @Override @Nonnull public final String getName() { return name; } /** * */ @Override public final int hashCode() { //It must be final. See Component#equals() return super.hashCode(); } /** * @return true if it is in a negative cycle. */ @Override public final boolean inNegativeCycle() { return inNegativeCycle; } /** * Removes a specific listener * * @param propertyName a {@link java.lang.String} object. * @param listener a {@link java.beans.PropertyChangeListener} object. */ public final void removeObserver(String propertyName, PropertyChangeListener listener) { pcs.removePropertyChangeListener(propertyName, listener); } /** * Sets true if the edge is in a negative cycle * * @param isInNegativeCycle the boolean status */ @Override public final void setInNegativeCycle(boolean isInNegativeCycle) { inNegativeCycle = isInNegativeCycle; } /** * Sets the name of the component. * * @param newName cannot be empty. If empty, it does nothing. * * @return the old name. */ //put firePropertyChange in the overriding method! @Override @Nonnull abstract public String setName(@Nonnull String newName); /** * @param c a {@link it.univr.di.cstnu.graph.Component} object. */ public final void takeIn(Component c) { setName(c.getName()); } /** * Return a string representation of labeled values. */ @Override public String toString() { return "〖%s〗".formatted(((name.isEmpty()) ? "" : name)); } /** * @return whether {@link #setName(String)} is currently locked by {@link #markNameManagedByGraph()}. */ final boolean isNameManagedByGraph() { return nameManagedByGraph; } /** * Locks {@link #setName(String)}. Called by every graph node/edge insertion path (including the fast * ones), never undone by removal. */ final void markNameManagedByGraph() { nameManagedByGraph = true; } /** * Sets the name without validation or notification. Reserved for graph backends that have already * validated the rename (uniqueness, Z-node protection, and so on) via {@code renameNode}/{@code * renameEdge}. * * @param newName the already-validated new name */ final void renameInternal(@Nonnull String newName) { name = newName; } }