diff --git a/src/main/java/blue/contract/packager/DependencyProcessor.java b/src/main/java/blue/contract/packager/DependencyProcessor.java index 86b58fe..b0f3842 100644 --- a/src/main/java/blue/contract/packager/DependencyProcessor.java +++ b/src/main/java/blue/contract/packager/DependencyProcessor.java @@ -8,6 +8,7 @@ import blue.contract.packager.utils.PackageMappingsUpdater; import blue.contract.packager.utils.TopologicalSorter; import blue.language.model.Node; +import blue.language.utils.BlueIdCalculator; import java.util.*; @@ -30,19 +31,53 @@ public DependencyProcessor(DependencyGraph graph) { public List process() { List processingOrder = graph.getProcessingOrder(); + + // First pass: Initialize packages and build initial mappings + for (String dirName : processingOrder) { + initializePackageAndMappings(dirName); + } + + // Second pass: Process nodes with mappings available for (String dirName : processingOrder) { processDirectory(dirName); } + return new ArrayList<>(processedPackages.values()); } - private void processDirectory(String dirName) { + private void initializePackageAndMappings(String dirName) { DirectoryNode dir = graph.getDirectories().get(dirName); BluePackage bluePackage = bluePackageInitializer.initialize(dirName, dir.getDependency(), processedPackages); processedPackages.put(dirName, bluePackage); + System.out.println("Initializing mappings for package: " + dirName); + + // Build initial mappings for all nodes in the package + for (Map.Entry entry : dir.getNodes().entrySet()) { + String nodeName = entry.getKey(); + Node node = entry.getValue(); + + // Calculate blueId for the original node + String blueId = BlueIdCalculator.calculateBlueId(node); + + System.out.println("Adding mapping for " + dirName + "::" + nodeName + " -> " + blueId); + + // Update mappings + packageMappingsUpdater.update(dirName, nodeName, node, blueId, processedPackages); + + // Verify mapping was added + String storedBlueId = bluePackage.getMappings().get(nodeName); + if (storedBlueId == null) { + System.err.println("WARNING: Mapping not added for " + nodeName + " in package " + dirName); + } + } + } + + private void processDirectory(String dirName) { + DirectoryNode dir = graph.getDirectories().get(dirName); Map nodes = new HashMap<>(dir.getNodes()); Set processed = new HashSet<>(); + List processingOrder; try { processingOrder = topologicalSorter.sort(nodes); @@ -60,15 +95,18 @@ private void processNode(String nodeName, Map nodes, Set p return; } - Node node = nodes.values().stream().filter(n -> n.getName().equals(nodeName)).findFirst().orElseThrow(); - Node preprocessedNode = nodePreprocessor.preprocess(node, dirName, processedPackages); - - nodes.put(nodeName, preprocessedNode); - - BluePackage bluePackage = processedPackages.get(dirName); - bluePackage.addPreprocessedNode(nodeName, preprocessedNode); - - packageMappingsUpdater.update(dirName, nodeName, preprocessedNode, processedPackages); - processed.add(nodeName); + Node node = nodes.get(nodeName); + try { + Node preprocessedNode = nodePreprocessor.preprocess(node, dirName, processedPackages); + nodes.put(nodeName, preprocessedNode); + + BluePackage bluePackage = processedPackages.get(dirName); + bluePackage.addPreprocessedNode(nodeName, preprocessedNode); + + processed.add(nodeName); + } catch (IllegalStateException e) { + throw new IllegalStateException("Error processing node '" + nodeName + "' in directory '" + dirName + "': " + + e.getMessage()); + } } } \ No newline at end of file diff --git a/src/main/java/blue/contract/packager/core/CoreTypeRegistry.java b/src/main/java/blue/contract/packager/core/CoreTypeRegistry.java new file mode 100644 index 0000000..1f87c13 --- /dev/null +++ b/src/main/java/blue/contract/packager/core/CoreTypeRegistry.java @@ -0,0 +1,47 @@ +package blue.contract.packager.core; + +import blue.language.model.Node; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import static blue.language.utils.UncheckedObjectMapper.YAML_MAPPER; + +public class CoreTypeRegistry { + private static Map coreTypes; + private static final String DEFAULT_BLUE_RESOURCE_PATH = "transformation/DefaultBlue.blue"; + + public static void initialize() { + if (coreTypes != null) return; + + try (InputStream inputStream = CoreTypeRegistry.class.getClassLoader() + .getResourceAsStream(DEFAULT_BLUE_RESOURCE_PATH)) { + + if (inputStream == null) { + throw new RuntimeException("Unable to find DefaultBlue.blue in classpath"); + } + + Node defaultBlue = YAML_MAPPER.readValue(inputStream, Node.class); + coreTypes = new HashMap<>(); + + // Get the first item which contains the mappings + Node firstItem = defaultBlue.getItems().get(0); + Node mappingsNode = firstItem.getProperties().get("mappings"); + + if (mappingsNode != null && mappingsNode.getProperties() != null) { + mappingsNode.getProperties().forEach((typeName, blueIdNode) -> { + coreTypes.put(typeName, blueIdNode.getValue().toString()); + }); + } + } catch (Exception e) { + throw new RuntimeException("Error loading core type mappings", e); + } + } + + public static String getBlueId(String typeName) { + if (coreTypes == null) { + initialize(); + } + return coreTypes.get(typeName); + } +} \ No newline at end of file diff --git a/src/main/java/blue/contract/packager/graphbuilder/ClasspathDependencyGraphBuilder.java b/src/main/java/blue/contract/packager/graphbuilder/ClasspathDependencyGraphBuilder.java index 97da42a..484110e 100644 --- a/src/main/java/blue/contract/packager/graphbuilder/ClasspathDependencyGraphBuilder.java +++ b/src/main/java/blue/contract/packager/graphbuilder/ClasspathDependencyGraphBuilder.java @@ -11,6 +11,7 @@ import static blue.contract.packager.BluePackageExporter.BLUE_FILE_EXTENSION; import static blue.contract.packager.BluePackageExporter.EXTENDS_FILE_NAME; import static blue.language.utils.UncheckedObjectMapper.YAML_MAPPER; +import static blue.contract.packager.BluePackageExporter.ROOT_DEPENDENCY; public class ClasspathDependencyGraphBuilder implements DependencyGraphBuilder { private final ClassLoader classLoader; @@ -46,7 +47,7 @@ public DependencyGraph buildDependencyGraph(String rootDir) throws IOException { private String readDependency(String path) throws IOException { try (InputStream is = classLoader.getResourceAsStream(path)) { if (is == null) { - throw new IOException("Dependency file not found: " + path); + return ROOT_DEPENDENCY; } Scanner scanner = new Scanner(is).useDelimiter("\\A"); String content = scanner.hasNext() ? scanner.next().trim() : ""; diff --git a/src/main/java/blue/contract/packager/graphbuilder/FileSystemDependencyGraphBuilder.java b/src/main/java/blue/contract/packager/graphbuilder/FileSystemDependencyGraphBuilder.java index dccb095..d1441a4 100644 --- a/src/main/java/blue/contract/packager/graphbuilder/FileSystemDependencyGraphBuilder.java +++ b/src/main/java/blue/contract/packager/graphbuilder/FileSystemDependencyGraphBuilder.java @@ -10,6 +10,7 @@ import static blue.contract.packager.BluePackageExporter.EXTENDS_FILE_NAME; import static blue.language.utils.UncheckedObjectMapper.YAML_MAPPER; +import static blue.contract.packager.BluePackageExporter.ROOT_DEPENDENCY; public class FileSystemDependencyGraphBuilder implements DependencyGraphBuilder { private final Path rootPath; @@ -43,7 +44,7 @@ public DependencyGraph buildDependencyGraph(String rootDir) throws IOException { private String readDependency(Path path) throws IOException { if (!Files.exists(path)) { - throw new IOException("Dependency file not found: " + path); + return ROOT_DEPENDENCY; } String content = Files.readString(path).trim(); if (content.isEmpty() || content.lines().count() != 1) { diff --git a/src/main/java/blue/contract/packager/model/BluePackage.java b/src/main/java/blue/contract/packager/model/BluePackage.java index ba4bda7..49db3de 100644 --- a/src/main/java/blue/contract/packager/model/BluePackage.java +++ b/src/main/java/blue/contract/packager/model/BluePackage.java @@ -8,15 +8,14 @@ public class BluePackage { private final String directoryName; private final Node packageContent; - private Map mappings; - private Map preprocessedNodes; + private final Map mappings; // Changed to final + private final Map preprocessedNodes; // Changed to final public BluePackage(String directoryName, Node packageContent) { this.directoryName = directoryName; this.packageContent = packageContent; this.mappings = new HashMap<>(); this.preprocessedNodes = new HashMap<>(); - } public String getDirectoryName() { @@ -28,11 +27,15 @@ public Node getPackageContent() { } public Map getMappings() { - return new HashMap<>(mappings); + return mappings; // Return direct reference instead of copy } public Map getPreprocessedNodes() { - return new HashMap<>(preprocessedNodes); + return preprocessedNodes; // Return direct reference instead of copy + } + + public void addMapping(String nodeName, String blueId) { + this.mappings.put(nodeName, blueId); } public void addPreprocessedNode(String nodeName, Node node) { diff --git a/src/main/java/blue/contract/packager/model/DependencyGraph.java b/src/main/java/blue/contract/packager/model/DependencyGraph.java index cbf5c24..2a81100 100644 --- a/src/main/java/blue/contract/packager/model/DependencyGraph.java +++ b/src/main/java/blue/contract/packager/model/DependencyGraph.java @@ -1,5 +1,6 @@ package blue.contract.packager.model; +import blue.contract.packager.utils.TypeReference; import blue.language.model.Node; import java.util.*; @@ -8,57 +9,113 @@ public class DependencyGraph { private Map directories; + private Map> additionalDependencies; + private static final String CORE_PACKAGE = "Core"; public DependencyGraph() { this.directories = new HashMap<>(); + this.additionalDependencies = new HashMap<>(); } public void addDirectory(String name, String dependency) { DirectoryNode node = new DirectoryNode(name); node.setDependency(dependency); directories.put(name, node); + additionalDependencies.put(name, new HashSet<>()); } public void addNode(String dirName, Node node) { directories.get(dirName).addNode(node); + findPackageReferences(dirName, node); } - public List getProcessingOrder() { - List order = new ArrayList<>(); - Set visited = new HashSet<>(); - Set recursionStack = new HashSet<>(); + private void findPackageReferences(String dirName, Node node) { + if (node == null) return; - for (String dirName : directories.keySet()) { - if (hasCyclicDependency(dirName, visited, recursionStack, order)) { - throw new IllegalStateException("Cyclic dependency detected in directory structure: " + - String.join(" -> ", recursionStack) + " -> " + dirName); + // Check type field + if (node.getType() != null && node.getType().isInlineValue()) { + String typeValue = node.getType().getValue().toString(); + TypeReference typeRef = new TypeReference(typeValue); + if (typeRef.isQualified() && !CORE_PACKAGE.equals(typeRef.getPackageName())) { + additionalDependencies.get(dirName).add(typeRef.getPackageName()); } } - return order; - } - - private boolean hasCyclicDependency(String dirName, Set visited, Set recursionStack, List order) { - if (recursionStack.contains(dirName)) { - return true; + // Recursively check items + if (node.getItems() != null) { + for (Node item : node.getItems()) { + findPackageReferences(dirName, item); + } } - if (visited.contains(dirName)) { - return false; + // Recursively check properties + if (node.getProperties() != null) { + for (Node propertyNode : node.getProperties().values()) { + findPackageReferences(dirName, propertyNode); + } } + } - visited.add(dirName); - recursionStack.add(dirName); - - DirectoryNode node = directories.get(dirName); - String dep = node.getDependency(); - if (!dep.equals(ROOT_DEPENDENCY) && hasCyclicDependency(dep, visited, recursionStack, order)) { - return true; + public List getProcessingOrder() { + Map inDegree = new HashMap<>(); + Map> adjacencyList = new HashMap<>(); + + // Initialize data structures + for (String dir : directories.keySet()) { + inDegree.put(dir, 0); + adjacencyList.put(dir, new ArrayList<>()); } - - recursionStack.remove(dirName); - order.add(dirName); - return false; + + // Build dependency graph + for (String dir : directories.keySet()) { + // Extension-based dependency + String dep = directories.get(dir).getDependency(); + if (!dep.equals(ROOT_DEPENDENCY)) { + adjacencyList.get(dep).add(dir); + inDegree.put(dir, inDegree.get(dir) + 1); + } + + // Additional dependencies (:: syntax) + for (String additionalDep : additionalDependencies.get(dir)) { + if (!CORE_PACKAGE.equals(additionalDep) && !directories.containsKey(additionalDep)) { + throw new IllegalStateException("Referenced package not found: " + additionalDep + + " (referenced from " + dir + ")"); + } + if (!CORE_PACKAGE.equals(additionalDep)) { + adjacencyList.get(additionalDep).add(dir); + inDegree.put(dir, inDegree.get(dir) + 1); + } + } + } + + // Topological sort using Kahn's algorithm + Queue queue = new LinkedList<>(); + List result = new ArrayList<>(); + + // Add all nodes with no dependencies to queue + for (Map.Entry entry : inDegree.entrySet()) { + if (entry.getValue() == 0) { + queue.add(entry.getKey()); + } + } + + while (!queue.isEmpty()) { + String current = queue.poll(); + result.add(current); + + for (String dependent : adjacencyList.get(current)) { + inDegree.put(dependent, inDegree.get(dependent) - 1); + if (inDegree.get(dependent) == 0) { + queue.add(dependent); + } + } + } + + if (result.size() != directories.size()) { + throw new IllegalStateException("Cyclic dependency detected in package structure"); + } + + return result; } public Map getDirectories() { diff --git a/src/main/java/blue/contract/packager/utils/NodePreprocessor.java b/src/main/java/blue/contract/packager/utils/NodePreprocessor.java index d5c8a39..8bf62c1 100644 --- a/src/main/java/blue/contract/packager/utils/NodePreprocessor.java +++ b/src/main/java/blue/contract/packager/utils/NodePreprocessor.java @@ -1,30 +1,87 @@ package blue.contract.packager.utils; +import blue.contract.packager.core.CoreTypeRegistry; import blue.contract.packager.model.BluePackage; import blue.language.NodeProvider; import blue.language.model.Node; import blue.language.preprocess.Preprocessor; import blue.language.provider.BasicNodeProvider; -import blue.language.utils.BlueIdCalculator; import blue.language.utils.NodeProviderWrapper; -import blue.language.utils.NodeToMapListOrValue; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; -import static blue.language.utils.UncheckedObjectMapper.YAML_MAPPER; - public class NodePreprocessor { - public Node preprocess(Node node, String dirName, Map processedPackages) { + public Node preprocess(Node node, String currentDirName, Map processedPackages) { + // Create a deep copy of the node to avoid modifying the original + Node processedNode = node.clone(); + + // Process all type references in the node + processTypeReferences(processedNode, currentDirName, processedPackages); + + // Continue with regular preprocessing List packageContents = new ArrayList<>(processedPackages.values().stream() .map(BluePackage::getPackageContent) .collect(Collectors.toList())); NodeProvider nodeProvider = NodeProviderWrapper.wrap(new BasicNodeProvider(packageContents)); Preprocessor preprocessor = new Preprocessor(nodeProvider); - Node blueNode = processedPackages.get(dirName).getPackageContent(); - return preprocessor.preprocess(node, blueNode); + Node blueNode = processedPackages.get(currentDirName).getPackageContent(); + return preprocessor.preprocess(processedNode, blueNode); + } + + private void processTypeReferences(Node node, String currentDirName, Map processedPackages) { + if (node == null) { + return; + } + + // Process type field + if (node.getType() != null && node.getType().isInlineValue()) { + processTypeField(node, currentDirName, processedPackages); + } + + // Process items recursively + if (node.getItems() != null) { + for (Node item : node.getItems()) { + processTypeReferences(item, currentDirName, processedPackages); + } + } + + // Process properties recursively + if (node.getProperties() != null) { + for (Node propertyNode : node.getProperties().values()) { + processTypeReferences(propertyNode, currentDirName, processedPackages); + } + } + } + + private void processTypeField(Node node, String currentDirName, Map processedPackages) { + String typeValue = node.getType().getValue().toString(); + TypeReference typeRef = new TypeReference(typeValue); + + if (typeRef.isQualified()) { + if ("Core".equals(typeRef.getPackageName())) { + String blueId = CoreTypeRegistry.getBlueId(typeRef.getTypeName()); + if (blueId == null) { + throw new IllegalStateException("Unknown Core type: " + typeRef.getTypeName()); + } + node.type(new Node().blueId(blueId)); + return; + } + BluePackage targetPackage = processedPackages.get(typeRef.getPackageName()); + if (targetPackage == null) { + throw new IllegalStateException("Referenced package not found: " + typeRef.getPackageName()); + } + + Map mappings = targetPackage.getMappings(); + String blueId = mappings.get(typeRef.getTypeName()); + if (blueId == null) { + throw new IllegalStateException("Type not found in package " + typeRef.getPackageName() + + ": " + typeRef.getTypeName()); + } + + // Directly set the blueId in the type node + node.type(new Node().blueId(blueId)); + } } } \ No newline at end of file diff --git a/src/main/java/blue/contract/packager/utils/PackageMappingsUpdater.java b/src/main/java/blue/contract/packager/utils/PackageMappingsUpdater.java index 32b0047..6487a80 100644 --- a/src/main/java/blue/contract/packager/utils/PackageMappingsUpdater.java +++ b/src/main/java/blue/contract/packager/utils/PackageMappingsUpdater.java @@ -2,15 +2,20 @@ import blue.contract.packager.model.BluePackage; import blue.language.model.Node; -import blue.language.utils.BlueIdCalculator; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public class PackageMappingsUpdater { - public void update(String dirName, String nodeName, Node preprocessedNode, Map processedPackages) { - String blueId = BlueIdCalculator.calculateBlueId(preprocessedNode); + public void update(String dirName, String nodeName, Node node, String blueId, Map processedPackages) { BluePackage bluePackage = processedPackages.get(dirName); + if (bluePackage == null) { + throw new IllegalStateException("Package not found: " + dirName); + } + + // Add to package mappings + bluePackage.addMapping(nodeName, blueId); + + // Update package content Node packageContent = bluePackage.getPackageContent(); Node packageMappingsNode = packageContent.getItems().get(1); @@ -26,6 +31,10 @@ public void update(String dirName, String nodeName, Node preprocessedNode, Map