<commit_before>package tools.vitruv.framework.tuid

import java.io.Serializable
import java.util.Arrays
import java.util.Collection
import java.util.HashMap
import java.util.HashSet
import java.util.LinkedList
import java.util.List
import java.util.Set
import org.apache.commons.lang.StringUtils
import com.google.common.base.Strings
import tools.vitruv.framework.util.VitruviusConstants
import tools.vitruv.framework.util.datatypes.ForwardHashedBackwardLinkedTree
import tools.vitruv.framework.util.datatypes.Pair

import static extension tools.vitruv.framework.util.bridges.CollectionBridge.*
import java.util.ArrayList
import java.util.Map
import tools.vitruv.framework.util.datatypes.ForwardHashedBackwardLinkedTree.Segment
import org.eclipse.emf.ecore.EObject

/** 
 * A class for Temporarily Unique IDentifiers (TUIDs) that internally uses a{@link ForwardHashedBackwardLinkedTree} to ensure that depending TUIDs are indirectly changed,
 * i.e. a call to toString() always returns the current TUID based on the links to its predecessors.
 * Implements the multiton design pattern to ensure that there exists only one TUID instance per
 * String key.<br/>
 * <br/>
 * Helpful thoughts for TUID modifications in file systems and Java projects:<br/>
 * <br/>
 * If a package is renamed in Java, then only the paths of all contained classifiers are affected.
 * The paths of subpackages are not affected but they are no longer subpackages. If a package is
 * renamed in Java, then the depth of its path may change arbitrarily. In such cases{@link #renameLastSegment(String)} should be called. <br/>
 * <br/>
 * If a folder is renamed, then the paths of all contained elements are affected but the depth may
 * not change. In such cases {@link #renameLastSegment(String)} should be called. <br/>
 * <br/>
 * If a package is moved in Java, then it may only be completely moved to another folder and
 * subpackages are not affected. It is not possible to move subpackages to another package. It is
 * however possible to move a package to a folder in which the package or a subpackage is already
 * existing, then the packages are merged. In such cases {@link #moveLastSegment(String)} should be
 * called. <br/>
 * <br/>
 * If a folder is moved, then the paths of all contained elements are affected and the depth may
 * change. If the destination folder already exists the containing elements of both folders are
 * merged. In such cases {@link #moveLastSegment(String)} should be called. <br/>
 * <br/>
 * @author kramerm
 */
final class TUID implements Serializable {
	
	protected static final long serialVersionUID = 5018494116382201707L
	
	static var SEGMENTS = generateForwardHashedBackwardLinkedTree()
	static val LAST_SEGMENT_2_TUID_INSTANCES_MAP = new HashMap<ForwardHashedBackwardLinkedTree<String>.Segment, TUID>()
	
	package static def reinitialize() {
		SEGMENTS = generateForwardHashedBackwardLinkedTree();
	}
	
	package def updateTuid(TUID newTuid) {
		if (this.equals(newTuid)) {
			return;
		}
		renameOrMoveLastSegment(newTuid)
	}
	
	public def updateTuid(EObject newObject) {
		TuidManager.instance.updateTuid(this, newObject);
	}
	
	def private static generateForwardHashedBackwardLinkedTree() {
		return new ForwardHashedBackwardLinkedTree<String>()
	}
	
	var ForwardHashedBackwardLinkedTree<String>.Segment lastSegment

	/** 
	 * Multiton classes should not have a public or default constructor. 
	 */
	private new(List<String> splitTUIDString) {
		this.lastSegment = SEGMENTS.addNewSegmentsWhereNecessary(splitTUIDString)
	}

	/** 
	 * Returns the unique TUID (instance) for the specified tuidString (key).
	 * @param tuidString
	 * @return the unique TUID for the specified tuidString
	 */
	def static synchronized List<TUID> getInstance(List<String> tuidStrings) {
		return tuidStrings.mapFixed[getInstance(it)]
	}

	/** 
	 * Returns the unique TUID (instance) for the specified tuidString (key).
	 * @param tuidString
	 * @return the unique TUID for the specified tuidString
	 */
	def static synchronized TUID getInstance(String tuidString) {
		return getInstance(tuidString, false)
	}
	
	def static valueOf(String tuidString) {
		return getInstance(tuidString)
	}

	def private static synchronized TUID getInstance(String tuidString, boolean recursively) {
		if (tuidString === null) {
			throw new IllegalArgumentException("The null string is no TUID!")
		} else {
			val splitTUIDString = split(tuidString)
			var lastSegmentOrPrefix = SEGMENTS.getMaximalPrefix(splitTUIDString)
			var TUID instance
			val lastSegmentOrPrefixString = if (lastSegmentOrPrefix != null) {
				lastSegmentOrPrefix.toString(VitruviusConstants.getTUIDSegmentSeperator());
			}
			if (lastSegmentOrPrefixString != null && lastSegmentOrPrefixString.equals(tuidString)) {
				// the complete specified tuidString was already mapped
				instance = LAST_SEGMENT_2_TUID_INSTANCES_MAP.get(lastSegmentOrPrefix)
				if (instance === null) {
					if (!recursively) {
						throw new IllegalStateException('''A TUID instance for the last segment '«»«lastSegmentOrPrefix»' should already have been mapped for the tuidString '«»«tuidString»'!''')
					}
				} else {
					return instance
				}
			}
			// a real prefix of the specified tuidString or nothing was already mapped (but not
			// the complete tuidString)
			instance = new TUID(splitTUIDString)
			var lastSegment = instance.getLastSegment()
			LAST_SEGMENT_2_TUID_INSTANCES_MAP.put(lastSegment, instance) // also create TUIDs for all prefixes of the specified tuidString and register them
			LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.put(lastSegment, #[instance]);
			val segmentIterator = lastSegment.iterator()
			var ForwardHashedBackwardLinkedTree<String>.Segment pivot
			while (segmentIterator.hasNext()) {
				pivot = segmentIterator.next()
				val TUID subInstance = getInstance(pivot.toString(VitruviusConstants.getTUIDSegmentSeperator()), true)
				LAST_SEGMENT_2_TUID_INSTANCES_MAP.put(subInstance.getLastSegment(), subInstance)
				LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.put(subInstance.getLastSegment(), #[subInstance]);
			}
			return instance
		}
	}
	
	/** 
	 * Changes the given tuid so that it points to the given newLastSegment.<br/>
	 * <b>ATTENTION: This changes the hashcode of the given tuid!</b>
	 * @param tuid
	 * @param newLastSegment
	 */
	def private static synchronized void updateInstance(TUID tuid, ForwardHashedBackwardLinkedTree<String>.Segment newLastSegment) {
		val oldSegment = tuid.lastSegment
		val tuidsForSegment = new ArrayList<TUID>(LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.remove(oldSegment));
		tuidsForSegment.add(tuid);
		for (representant : tuidsForSegment) {
			representant.lastSegment = newLastSegment;
		}
		LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.put(newLastSegment, tuidsForSegment);
		LAST_SEGMENT_2_TUID_INSTANCES_MAP.remove(oldSegment)
		LAST_SEGMENT_2_TUID_INSTANCES_MAP.putIfAbsent(newLastSegment, tuid);
	}

	public static Map<Segment, List<TUID>> LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP = new HashMap<Segment, List<TUID>>();

	def private static List<String> split(String tuidString) {
		val seperator = VitruviusConstants.getTUIDSegmentSeperator()
		// TODO replace this possibly ArrayList with a LinkList if performance is not sufficient
		return Arrays.asList(tuidString.split(seperator))
	}

	def private ForwardHashedBackwardLinkedTree<String>.Segment getLastSegment() {
		return this.lastSegment
	}
	
	// TODO MK Xtend improvement: define null as default value for before and after using an active annotation in all rename and move methods
	/**<commit_msg>Fix a bug in the segment to TUID map update.<commit_after>package tools.vitruv.framework.tuid

import java.io.Serializable
import java.util.Arrays
import java.util.Collection
import java.util.HashMap
import java.util.HashSet
import java.util.LinkedList
import java.util.List
import java.util.Set
import org.apache.commons.lang.StringUtils
import com.google.common.base.Strings
import tools.vitruv.framework.util.VitruviusConstants
import tools.vitruv.framework.util.datatypes.ForwardHashedBackwardLinkedTree
import tools.vitruv.framework.util.datatypes.Pair

import java.util.ArrayList
import java.util.Map
import tools.vitruv.framework.util.datatypes.ForwardHashedBackwardLinkedTree.Segment
import org.eclipse.emf.ecore.EObject

/** 
 * A class for Temporarily Unique IDentifiers (TUIDs) that internally uses a{@link ForwardHashedBackwardLinkedTree} to ensure that depending TUIDs are indirectly changed,
 * i.e. a call to toString() always returns the current TUID based on the links to its predecessors.
 * Implements the multiton design pattern to ensure that there exists only one TUID instance per
 * String key.<br/>
 * <br/>
 * Helpful thoughts for TUID modifications in file systems and Java projects:<br/>
 * <br/>
 * If a package is renamed in Java, then only the paths of all contained classifiers are affected.
 * The paths of subpackages are not affected but they are no longer subpackages. If a package is
 * renamed in Java, then the depth of its path may change arbitrarily. In such cases{@link #renameLastSegment(String)} should be called. <br/>
 * <br/>
 * If a folder is renamed, then the paths of all contained elements are affected but the depth may
 * not change. In such cases {@link #renameLastSegment(String)} should be called. <br/>
 * <br/>
 * If a package is moved in Java, then it may only be completely moved to another folder and
 * subpackages are not affected. It is not possible to move subpackages to another package. It is
 * however possible to move a package to a folder in which the package or a subpackage is already
 * existing, then the packages are merged. In such cases {@link #moveLastSegment(String)} should be
 * called. <br/>
 * <br/>
 * If a folder is moved, then the paths of all contained elements are affected and the depth may
 * change. If the destination folder already exists the containing elements of both folders are
 * merged. In such cases {@link #moveLastSegment(String)} should be called. <br/>
 * <br/>
 * @author kramerm
 */
final class TUID implements Serializable {
	
	protected static final long serialVersionUID = 5018494116382201707L
	
	static var SEGMENTS = generateForwardHashedBackwardLinkedTree()
	static val LAST_SEGMENT_2_TUID_INSTANCES_MAP = new HashMap<ForwardHashedBackwardLinkedTree<String>.Segment, TUID>()
	
	package static def reinitialize() {
		SEGMENTS = generateForwardHashedBackwardLinkedTree();
	}
	
	package def updateTuid(TUID newTuid) {
		if (this.equals(newTuid)) {
			return;
		}
		renameOrMoveLastSegment(newTuid)
	}
	
	public def updateTuid(EObject newObject) {
		TuidManager.instance.updateTuid(this, newObject);
	}
	
	def private static generateForwardHashedBackwardLinkedTree() {
		return new ForwardHashedBackwardLinkedTree<String>()
	}
	
	var ForwardHashedBackwardLinkedTree<String>.Segment lastSegment

	/** 
	 * Multiton classes should not have a public or default constructor. 
	 */
	private new(List<String> splitTUIDString) {
		this.lastSegment = SEGMENTS.addNewSegmentsWhereNecessary(splitTUIDString)
	}

	/** 
	 * Returns the unique TUID (instance) for the specified tuidString (key).
	 * @param tuidString
	 * @return the unique TUID for the specified tuidString
	 */
	def static synchronized TUID getInstance(String tuidString) {
		return getInstance(tuidString, false)
	}
	
	def static valueOf(String tuidString) {
		return getInstance(tuidString)
	}

	def private static synchronized TUID getInstance(String tuidString, boolean recursively) {
		if (tuidString === null) {
			throw new IllegalArgumentException("The null string is no TUID!")
		} else {
			val splitTUIDString = split(tuidString)
			var lastSegmentOrPrefix = SEGMENTS.getMaximalPrefix(splitTUIDString)
			var TUID instance
			val lastSegmentOrPrefixString = if (lastSegmentOrPrefix != null) {
				lastSegmentOrPrefix.toString(VitruviusConstants.getTUIDSegmentSeperator());
			}
			if (lastSegmentOrPrefixString != null && lastSegmentOrPrefixString.equals(tuidString)) {
				// the complete specified tuidString was already mapped
				instance = LAST_SEGMENT_2_TUID_INSTANCES_MAP.get(lastSegmentOrPrefix)
				if (instance === null) {
					if (!recursively) {
						throw new IllegalStateException('''A TUID instance for the last segment '«»«lastSegmentOrPrefix»' should already have been mapped for the tuidString '«»«tuidString»'!''')
					}
				} else {
					return instance
				}
			}
			// a real prefix of the specified tuidString or nothing was already mapped (but not
			// the complete tuidString)
			instance = new TUID(splitTUIDString)
			var lastSegment = instance.getLastSegment()
			LAST_SEGMENT_2_TUID_INSTANCES_MAP.put(lastSegment, instance) // also create TUIDs for all prefixes of the specified tuidString and register them
			val lastSegmentsMapUnmodifiable = LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.get(lastSegment);
			val lastSegmentsMap = new ArrayList(if (lastSegmentsMapUnmodifiable != null) lastSegmentsMapUnmodifiable else #[]);
			lastSegmentsMap.add(instance); 
			LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.put(lastSegment, lastSegmentsMap);
			val segmentIterator = lastSegment.iterator()
			var ForwardHashedBackwardLinkedTree<String>.Segment pivot
			while (segmentIterator.hasNext()) {
				pivot = segmentIterator.next()
				val TUID subInstance = getInstance(pivot.toString(VitruviusConstants.getTUIDSegmentSeperator()), true)
				LAST_SEGMENT_2_TUID_INSTANCES_MAP.put(subInstance.getLastSegment(), subInstance)
				val lastSegmentsMapUnmodifiableSub = LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.get(subInstance.getLastSegment());
				val lastSegmentsMapSub = new ArrayList(if (lastSegmentsMapUnmodifiableSub != null) lastSegmentsMapUnmodifiableSub else #[]);
				lastSegmentsMapSub.add(subInstance); 
				LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.put(subInstance.getLastSegment(), lastSegmentsMapSub);
			}
			return instance
		}
	}
	
	/** 
	 * Changes the given tuid so that it points to the given newLastSegment.<br/>
	 * <b>ATTENTION: This changes the hashcode of the given tuid!</b>
	 * @param tuid
	 * @param newLastSegment
	 */
	def private static synchronized void updateInstance(TUID tuid, ForwardHashedBackwardLinkedTree<String>.Segment newLastSegment) {
		val oldSegment = tuid.lastSegment
		val tuidsForNewSegmentUnmodifiable = LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.remove(newLastSegment);
		val tuidsForNewSegment = new ArrayList<TUID>(if (tuidsForNewSegmentUnmodifiable != null) tuidsForNewSegmentUnmodifiable else #[]);
		val tuidsForOldSegment = new ArrayList<TUID>(LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.remove(oldSegment));
		tuidsForNewSegment.addAll(tuidsForOldSegment);
		for (representant : tuidsForOldSegment) {
			representant.lastSegment = newLastSegment;
		}
		LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.put(newLastSegment, tuidsForNewSegment);
		LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP.remove(oldSegment);
		LAST_SEGMENT_2_TUID_INSTANCES_MAP.remove(oldSegment)
		LAST_SEGMENT_2_TUID_INSTANCES_MAP.putIfAbsent(newLastSegment, tuid);
	}

	private static Map<Segment, List<TUID>> LAST_SEGMENT_2_ALL_TUID_INSTANCES_MAP = new HashMap<Segment, List<TUID>>();

	def private static List<String> split(String tuidString) {
		val seperator = VitruviusConstants.getTUIDSegmentSeperator()
		// TODO replace this possibly ArrayList with a LinkList if performance is not sufficient
		return Arrays.asList(tuidString.split(seperator))
	}

	def private ForwardHashedBackwardLinkedTree<String>.Segment getLastSegment() {
		return this.lastSegment
	}
	
	// TODO MK Xtend improvement: define null as default value for before and after using an active annotation in all rename and move methods
	/**
{'fraction_non_alphanumeric': 0.07772384723464044, 'fraction_numerical': 0.0040873674798824885, 'mean_word_length': 5.114408434205388, 'pattern_counts': {'":': 0, '<': 64, '<?xml version=': 0, '>': 64, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5220577', 'n_tokens_mistral': 5034, 'n_tokens_neox': 4606, 'n_words': 1397}