Impact - A Replacement FembicReader

From PeformIQ Wiki
Jump to navigation Jump to search

The Fembic Reader

Here is prototype code for a replacement tp the FembicReader which uses java.nio and java.util.regex.


/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License or (at your option) any later version.
 *
 * 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 GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, inc., 59 Temple Place, Suite 330, Boston MA 02111-1307
 * USA
 */

package run.readers;

// ----------------------------------------------------------------------

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


// ----------------------------------------------------------------------

import run.Constraint;
import run.Controlset;
import run.Element;
import run.Load;
import run.Material;
import run.Node;
import run.Reader;
import run.RplVector;
import run.Token;
import run.TrackWriter;
import run.Tracker;
import run.Writer;

import uka.karmi.rmi.RemoteException;

import jp.lang.RemoteObject;


// ----------------------------------------------------------------------

/**
 * Fembic file reader - modernized version using BufferedReader and regex.
 * Original authors: Yuriy Mikhaylovskiy, Jonas Forssell
 * Modernized: 2025
 */
public class FembicReader extends Reader {

    private BufferedReader br;
    private final String filename;
    private final Set<String> keywords = Set.of(
            "TITLE", "CONTROLS", "ELEMENTS", "NODES", "LOADS",
            "CONSTRAINTS", "MATERIALS", "TRACKERS", "GROUPS", "GEOMETRY"
    );

    // Parsing state
    private boolean inBlock = false;
    private boolean elementInBlock = false;
    private boolean trackerInBlock = false;
    private boolean materialInBlock = false;
    private boolean controlInBlock = false;
    private boolean constraintInBlock = false;

    // Current type being parsed
    private String currentElementType;
    private String currentTrackerType;
    private String currentConstraintType;

    // Line-based parsing state
    private List<String> fileLines;
    private int currentLineIndex;
    private int currentLineNumber;

    // Regex patterns for parsing
    private static final Pattern BLOCK_HEADER_PATTERN = 
            Pattern.compile("^(\\w+)\\s+OF\\s+TYPE\\s+(\\w+)\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern SIMPLE_BLOCK_PATTERN = 
            Pattern.compile("^(\\w+)\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern NUMBER_PATTERN = 
            Pattern.compile("^-?\\d+(\\.\\d+)?([eE][+-]?\\d+)?$");
    private static final Pattern PARAM_EQUALS_PATTERN = 
            Pattern.compile("(\\w+)\\s*=\\s*(.+)");

    // Temporary objects
    private Node temporaryNode;
    private Load temporaryLoad;

    /**
     * Constructor - stores filename for later opening.
     */
    public FembicReader(String fn) {
        this.filename = fn;
    }

    /**
     * Closes the reader.
     */
    public void close() {
        if (br != null) {
            try {
                br.close();
            } catch (IOException ioe) {
                System.out.println("An IOException has occurred when closing the indata file");
            }
        }
        fileLines = null;
        currentLineIndex = 0;
    }

    /**
     * Opens the file and loads all lines into memory for parsing.
     */
    public void open() {
        // Reset parsing state
        inBlock = false;
        controlInBlock = false;
        constraintInBlock = false;
        elementInBlock = false;
        trackerInBlock = false;
        materialInBlock = false;
        currentLineIndex = 0;

        try {
            Path path = Path.of(filename);
            fileLines = Files.readAllLines(path);
            br = Files.newBufferedReader(path);
        } catch (IOException e) {
            System.out.println("Failed to open Fembic file: " + e.getMessage());
            fileLines = new ArrayList<>();
        }
    }

    /**
     * Checks if a string is a keyword.
     */
    private boolean isAKeyword(String param) {
        return keywords.contains(param.toUpperCase());
    }

    /**
     * Gets the next non-empty, non-comment line.
     * Returns null if EOF reached.
     */
    private String getNextLine() {
        while (currentLineIndex < fileLines.size()) {
            String line = fileLines.get(currentLineIndex).trim();
            currentLineNumber = currentLineIndex + 1;
            currentLineIndex++;

            // Skip empty lines and comments
            if (!line.isEmpty() && !line.startsWith("#") && !line.startsWith("//")) {
                return line;
            }
        }
        return null;
    }

    /**
     * Peeks at the next non-empty line without consuming it.
     */
    private String peekNextLine() {
        int tempIndex = currentLineIndex;
        while (tempIndex < fileLines.size()) {
            String line = fileLines.get(tempIndex).trim();
            tempIndex++;
            if (!line.isEmpty() && !line.startsWith("#") && !line.startsWith("//")) {
                return line;
            }
        }
        return null;
    }

    /**
     * Pushes back to re-read the current line.
     */
    private void pushBack() {
        if (currentLineIndex > 0) {
            currentLineIndex--;
        }
    }

    /**
     * Searches for a block with the given keyword.
     * Returns true if found, false if EOF reached.
     */
    private boolean findBlock(String blockKeyword) {
        String line;
        while ((line = getNextLine()) != null) {
            String upperLine = line.toUpperCase();
            if (upperLine.startsWith(blockKeyword.toUpperCase())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Searches for a typed block (e.g., "ELEMENTS OF TYPE BEAM").
     * Returns the type if found, null otherwise.
     */
    private String findTypedBlock(String blockKeyword) {
        String line;
        while ((line = getNextLine()) != null) {
            Matcher matcher = BLOCK_HEADER_PATTERN.matcher(line);
            if (matcher.matches() && matcher.group(1).equalsIgnoreCase(blockKeyword)) {
                return matcher.group(2).toUpperCase();
            }
        }
        return null;
    }

    /**
     * Checks if a string represents a number.
     */
    private boolean isNumber(String s) {
        return NUMBER_PATTERN.matcher(s).matches();
    }

    /**
     * Parses a number from a string.
     */
    private double parseNumber(String s) {
        return Double.parseDouble(s);
    }

    /**
     * Tokenizes a line into Token array for parsing.
     * Handles parameter sets in brackets and equals signs.
     */
    public Token[] tokenize(String line) {
        List<Token> tokens = new ArrayList<>();

        // Split on whitespace but preserve bracket contents
        List<String> parts = splitPreservingBrackets(line);

        for (String part : parts) {
            part = part.trim();
            if (part.isEmpty()) continue;

            // Handle bracket sets [x,y,z]
            if (part.startsWith("[") && part.endsWith("]")) {
                tokens.add(new Token(part));
                continue;
            }

            // Handle param=value (no spaces around =)
            if (part.contains("=") && part.length() > 1) {
                int eqIndex = part.indexOf("=");
                if (eqIndex > 0) {
                    tokens.add(new Token(part.substring(0, eqIndex)));
                }
                tokens.add(new Token("="));
                if (eqIndex < part.length() - 1) {
                    String value = part.substring(eqIndex + 1);
                    addValueToken(tokens, value);
                }
                continue;
            }

            // Regular token
            addValueToken(tokens, part);
        }

        return tokens.toArray(new Token[0]);
    }

    /**
     * Splits a string on whitespace while preserving content within brackets.
     */
    private List<String> splitPreservingBrackets(String line) {
        List<String> parts = new ArrayList<>();
        StringBuilder current = new StringBuilder();
        int bracketDepth = 0;

        for (char c : line.toCharArray()) {
            if (c == '[') {
                bracketDepth++;
                current.append(c);
            } else if (c == ']') {
                bracketDepth--;
                current.append(c);
            } else if (Character.isWhitespace(c) && bracketDepth == 0) {
                if (current.length() > 0) {
                    parts.add(current.toString());
                    current = new StringBuilder();
                }
            } else {
                current.append(c);
            }
        }

        if (current.length() > 0) {
            parts.add(current.toString());
        }

        return parts;
    }

    /**
     * Adds a token, determining if it's a number or word.
     */
    private void addValueToken(List<Token> tokens, String value) {
        if (isNumber(value)) {
            tokens.add(new Token(parseNumber(value)));
        } else {
            tokens.add(new Token(value));
        }
    }

    /**
     * Reads and parses the control set.
     */
    public void getControlSet(Controlset temporaryControlset) throws IllegalArgumentException {
        try {
            if (!controlInBlock) {
                if (!findBlock("CONTROLS")) {
                    throw new ParseException("No controls block found or missing controls", currentLineNumber);
                }
                controlInBlock = true;
            }

            // Read control lines until we hit a keyword
            String line;
            while ((line = peekNextLine()) != null) {
                String[] parts = line.split("\\s+");
                if (parts.length > 0 && isAKeyword(parts[0])) {
                    break;
                }

                line = getNextLine();
                if (line != null && !line.isEmpty()) {
                    temporaryControlset.parse_Fembic(tokenize(line), currentLineNumber);
                }
            }

            temporaryControlset.checkIndata();

        } catch (ParseException e) {
            throw new IllegalArgumentException("Failed to generate control set: " + e.getMessage());
        }
    }

    /**
     * Reads the next element from the file.
     */
    @SuppressWarnings("rawtypes")
    public Element getNextElement(RplVector materiallist, RplVector nodelist,
                                   RplVector loadlist, Hashtable nodetable) throws ParseException {
        try {
            // Find next element block if needed
            String line = peekNextLine();
            if (line == null) {
                throw new ParseException("No elements block found or missing elements", currentLineNumber);
            }

            // Check if we need to find a new element block
            String[] parts = line.split("\\s+");
            if (!elementInBlock || (parts.length > 0 && !isNumber(parts[0]))) {
                // Search for next ELEMENTS OF TYPE block
                String type = findTypedBlock("ELEMENTS");
                if (type == null) {
                    throw new ParseException("No elements block found or missing elements", currentLineNumber);
                }
                currentElementType = type;
                elementInBlock = true;
                System.out.println("Element block found!");
            }

            // Read the element line
            line = getNextLine();
            if (line == null) {
                throw new ParseException("Unexpected end of file while reading elements", currentLineNumber);
            }

            parts = line.split("\\s+", 2);
            if (parts.length < 1 || !isNumber(parts[0])) {
                throw new ParseException("Expected element number", currentLineNumber);
            }

            int elementNumber = (int) parseNumber(parts[0]);
            String restOfLine = parts.length > 1 ? parts[1] : "";

            Element temporaryElement = Element.getElementOfType_Fembic(currentElementType);
            temporaryElement.setNumber(elementNumber);
            temporaryElement.parse_Fembic(tokenize(restOfLine), currentLineNumber,
                    nodelist, materiallist, loadlist, nodetable);
            temporaryElement.checkIndata();

            return temporaryElement;

        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage() + " in line: " + currentLineNumber);
        }
    }

    /**
     * Reads the next tracker from the file.
     */
    public Tracker getNextTracker(RplVector nodelist, RplVector elementlist) throws ParseException {
        try {
            String line = peekNextLine();
            if (line == null) {
                throw new ParseException("No trackers block found or missing trackers", currentLineNumber);
            }

            String[] parts = line.split("\\s+");
            if (!trackerInBlock || (parts.length > 0 && !isNumber(parts[0]))) {
                String type = findTypedBlock("TRACKERS");
                if (type == null) {
                    throw new ParseException("No trackers block found or missing trackers", currentLineNumber);
                }
                currentTrackerType = type;
                trackerInBlock = true;
                System.out.println("Tracker block found!");
            }

            line = getNextLine();
            if (line == null) {
                throw new ParseException("Unexpected end of file while reading trackers", currentLineNumber);
            }

            parts = line.split("\\s+", 2);
            if (parts.length < 1 || !isNumber(parts[0])) {
                throw new ParseException("Expected tracker number", currentLineNumber);
            }

            int trackerNumber = (int) parseNumber(parts[0]);
            String restOfLine = parts.length > 1 ? parts[1] : "";

            Tracker temporaryTracker = Tracker.getTrackerOfType_Fembic(currentTrackerType);
            temporaryTracker.setNumber(trackerNumber);
            temporaryTracker.parse_Fembic(tokenize(restOfLine), currentLineNumber, nodelist, elementlist);
            temporaryTracker.checkIndata();

            return temporaryTracker;

        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage() + " in line: " + currentLineNumber);
        }
    }

    /**
     * Reads the next constraint from the file.
     */
    public Constraint getNextConstraint(RplVector nodelist) throws ParseException {
        try {
            String line = peekNextLine();
            if (line == null) {
                throw new ParseException("No constraint block found or missing constraints", currentLineNumber);
            }

            String[] parts = line.split("\\s+");
            if (!constraintInBlock || (parts.length > 0 && isAKeyword(parts[0]))) {
                String type = findTypedBlock("CONSTRAINTS");
                if (type == null) {
                    throw new ParseException("No constraint block found or missing constraints", currentLineNumber);
                }
                currentConstraintType = type;
                constraintInBlock = true;
                System.out.println("Constraint block found!");
            }

            line = getNextLine();
            if (line == null) {
                throw new ParseException("Unexpected end of file while reading constraints", currentLineNumber);
            }

            parts = line.split("\\s+", 2);
            if (parts.length < 1) {
                throw new ParseException("Expected constraint name", currentLineNumber);
            }

            String constraintName = parts[0].toUpperCase().trim();
            String restOfLine = parts.length > 1 ? parts[1] : "";

            Constraint temporaryConstraint = Constraint.getConstraintOfType_Fembic(currentConstraintType);
            temporaryConstraint.setName(constraintName);
            temporaryConstraint.parse_Fembic(tokenize(restOfLine), currentLineNumber, nodelist);
            temporaryConstraint.checkIndata();

            return temporaryConstraint;

        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage() + " in line: " + currentLineNumber);
        }
    }

    /**
     * Reads the next load from the file.
     */
    public Load getNextLoad(RplVector nodelist) throws ParseException {
        try {
            if (!inBlock) {
                if (!findBlock("LOADS")) {
                    throw new ParseException("No loads block found or missing loads", currentLineNumber);
                }
                inBlock = true;
            }

            String line = getNextLine();
            if (line == null) {
                throw new ParseException("Unexpected end of file while reading loads", currentLineNumber);
            }

            String[] parts = line.split("\\s+", 2);
            if (parts.length < 1) {
                throw new ParseException("No load name found", currentLineNumber);
            }

            temporaryLoad = new Load();
            temporaryLoad.name = parts[0].toUpperCase();
            String restOfLine = parts.length > 1 ? parts[1] : "";

            temporaryLoad.parse_Fembic(tokenize(restOfLine), currentLineNumber);
            temporaryLoad.checkIndata();

            return temporaryLoad;

        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage() + " in line: " + currentLineNumber);
        }
    }

    /**
     * Reads the next material from the file.
     */
    public Material getNextMaterial() throws ParseException {
        try {
            String line = peekNextLine();
            if (line == null) {
                throw new ParseException("No materials block found or missing materials", currentLineNumber);
            }

            String[] parts = line.split("\\s+");
            if (!materialInBlock || (parts.length > 0 && isAKeyword(parts[0]))) {
                String type = findTypedBlock("MATERIALS");
                if (type == null) {
                    throw new ParseException("No materials block found or missing materials", currentLineNumber);
                }
                currentElementType = type; // reusing for material type
                materialInBlock = true;
                System.out.println("Block found!");
            }

            line = getNextLine();
            if (line == null) {
                throw new ParseException("Unexpected end of file while reading materials", currentLineNumber);
            }

            parts = line.split("\\s+", 2);
            if (parts.length < 1) {
                throw new ParseException("Expected material name", currentLineNumber);
            }

            String materialName = parts[0].toUpperCase();
            String restOfLine = parts.length > 1 ? parts[1] : "";

            Material temporaryMaterial = Material.getMaterialOfType_Fembic(currentElementType);
            temporaryMaterial.setName(materialName);
            temporaryMaterial.parse_Fembic(tokenize(restOfLine), currentLineNumber);
            temporaryMaterial.checkIndata();

            return temporaryMaterial;

        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage() + " in line: " + currentLineNumber);
        }
    }

    /**
     * Reads the next node from the file.
     */
    public Node getNextNode(RplVector constraintlist, RplVector loadlist) throws ParseException {
        try {
            String line = peekNextLine();
            if (line == null) {
                throw new ParseException("No nodes block found or missing nodes", currentLineNumber);
            }

            String[] parts = line.split("\\s+");
            if (!inBlock || (parts.length > 0 && !isNumber(parts[0]))) {
                if (!findBlock("NODES")) {
                    throw new ParseException("No nodes block found or missing nodes", currentLineNumber);
                }
                inBlock = true;
            }

            line = getNextLine();
            if (line == null) {
                throw new ParseException("Unexpected end of file while reading nodes", currentLineNumber);
            }

            parts = line.split("\\s+", 2);
            if (parts.length < 1 || !isNumber(parts[0])) {
                throw new ParseException("Expected node number", currentLineNumber);
            }

            int nodeNumber = (int) parseNumber(parts[0]);
            String restOfLine = parts.length > 1 ? parts[1] : "";

            temporaryNode = new Node();
            temporaryNode.setNumber(nodeNumber);
            temporaryNode.parse_Fembic(tokenize(restOfLine), currentLineNumber, constraintlist, loadlist);
            temporaryNode.checkIndata();

            return temporaryNode;

        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage() + " in line: " + currentLineNumber);
        }
    }

    // ========== Count Methods ==========

    /**
     * Counts items in a simple block (keyword followed by lines).
     */
    private int countInSimpleBlock(String blockKeyword, boolean expectNumber) throws ParseException {
        int count = 0;
        open();

        try {
            for (String line : fileLines) {
                line = line.trim();
                if (line.isEmpty()) continue;

                String upperLine = line.toUpperCase();
                if (upperLine.startsWith(blockKeyword.toUpperCase())) {
                    // Check it's just the keyword (simple block)
                    Matcher simpleMatcher = SIMPLE_BLOCK_PATTERN.matcher(line);
                    if (simpleMatcher.matches()) {
                        // Found the block, now count items
                        int idx = fileLines.indexOf(line) + 1;
                        while (idx < fileLines.size()) {
                            String itemLine = fileLines.get(idx).trim();
                            idx++;

                            if (itemLine.isEmpty()) continue;

                            String[] parts = itemLine.split("\\s+");
                            if (parts.length > 0 && isAKeyword(parts[0])) {
                                break;
                            }

                            if (expectNumber) {
                                if (isNumber(parts[0])) {
                                    count++;
                                }
                            } else {
                                if (!isAKeyword(parts[0])) {
                                    count++;
                                }
                            }
                        }
                    }
                }
            }
        } finally {
            close();
        }

        System.out.println("Found " + count + " " + blockKeyword.toLowerCase() + "s");
        return count;
    }

    /**
     * Counts items in a typed block (keyword OF TYPE xxx).
     */
    private int countInTypedBlock(String blockKeyword, boolean expectNumber) throws ParseException {
        int count = 0;
        open();

        try {
            for (int i = 0; i < fileLines.size(); i++) {
                String line = fileLines.get(i).trim();
                if (line.isEmpty()) continue;

                Matcher matcher = BLOCK_HEADER_PATTERN.matcher(line);
                if (matcher.matches() && matcher.group(1).equalsIgnoreCase(blockKeyword)) {
                    // Found a typed block, count items
                    int idx = i + 1;
                    while (idx < fileLines.size()) {
                        String itemLine = fileLines.get(idx).trim();
                        idx++;

                        if (itemLine.isEmpty()) continue;

                        String[] parts = itemLine.split("\\s+");
                        if (parts.length > 0 && isAKeyword(parts[0])) {
                            break;
                        }

                        if (expectNumber) {
                            if (isNumber(parts[0])) {
                                count++;
                            }
                        } else {
                            count++;
                        }
                    }
                }
            }
        } finally {
            close();
        }

        System.out.println("Found " + count + " " + blockKeyword.toLowerCase() + "s");
        return count;
    }

    public int numberOfControls() throws ParseException {
        int count = countInSimpleBlock("CONTROLS", false);
        if (count == 0) {
            throw new ParseException("No controls found", 0);
        }
        return count;
    }

    public int numberOfNodes() throws ParseException {
        int count = countInSimpleBlock("NODES", true);
        if (count == 0) {
            throw new ParseException("No nodes found", 0);
        }
        return count;
    }

    public int numberOfLoads() throws ParseException {
        return countInSimpleBlock("LOADS", false);
    }

    public int numberOfGroups() throws ParseException {
        return countInSimpleBlock("GROUPS", false);
    }

    public int numberOfConstraints() throws ParseException {
        return countInTypedBlock("CONSTRAINTS", false);
    }

    public int numberOfElements() throws ParseException {
        int count = countInTypedBlock("ELEMENTS", true);
        if (count == 0) {
            throw new ParseException("No elements found", 0);
        }
        return count;
    }

    public int numberOfTrackers() throws ParseException {
        return countInTypedBlock("TRACKERS", true);
    }

    public int numberOfMaterials() throws ParseException {
        int count = countInTypedBlock("MATERIALS", false);
        if (count == 0) {
            throw new ParseException("No materials found", 0);
        }
        return count;
    }

    // ========== Writer Methods ==========

    public Writer getWriter(RplVector nodelist, RplVector elementlist,
                            Controlset control, RemoteObject[] cluster_nodes) throws RemoteException {
        Writer writer = Writer.getWriterOfType_Fembic(control.getWriter(), nodelist,
                elementlist, cluster_nodes);
        writer.checkIndata();
        return writer;
    }

    public TrackWriter getTrackWriter(RplVector trackerlist,
                                       Controlset control, RemoteObject[] cluster_nodes) throws RemoteException {
        TrackWriter trackWriter = TrackWriter.getTrackWriterOfType_Fembic(
                control.getTrackWriter(), trackerlist);
        trackWriter.checkIndata();
        return trackWriter;
    }

    /**
     * Pre-processing hook (currently empty).
     */
    public void preProcess() throws ParseException {
        // No preprocessing needed
    }
}