Bu konu çözüldü olarak işaretlenmiştir. Çözülmediğini düşünüyorsanız konuyu rapor edebilirsiniz.
Katılım
22 Aralık 2023
Mesajlar
860
Makaleler
4
Çözümler
5
Beğeniler
925
Selam,

Öncelikle başlığa aldanmayın. Hayır, ikisinde de Java tam yüklü, herhangi bir yazılımsal ya da donanımsal problem söz konusu değil. İkisi de şahsi bilgisayarım. İkisinde de düzenli olarak Java geliştiriyorum, ancak ilk defa bir problemi çözemeyip pes ettim. Aşağıya bıraktığım çıktılar, çok basit seviyedeki bir işletim sistemi dosya sistemine ait. Kendine ait basit bir yetkilendirmesi de var; hash kontrolü ile içeri kullanıcıyı alıyor, sonra dosya yaratıp içine bir şeyler yazabiliyorsun. Gelelim probleme:

Screenshot 2025-09-09 at 1.28.55 AM.webp
Screenshot 2025-09-09 at 1.29.56 AM.webp

6 saattir bunu çözmeye çalışıyorum ve aynı kaynak kodunu macOS Terminal’de çalıştırdığımda yağ gibi aktığını fark ettim. Soldaki Terminal, sağdaki CMD; sağda yazılar sanki bir yerde print – println hatası var mış gibi birbiri içerisinde geçiyor. Solda ise, tıpkı IDE’de aldığım gibi tertemiz çıktılar alıyorum. Zaten soldaki görsele bakarak sağ tarafın ne kadar problemli olduğunu anlayabilirsiniz hocalarım.
 
Son düzenleyen: Moderatör:
Çözüm
Çok teşekkürler hocam yeni bir şey kattın.
Rica ederim. Sorunu nasil tespit ettigime dair sureci anlatacak olursam;
Username girerken, girilen metni editleyebilirken, menuLoop icerisinde girilen metni editlemeye kalktigimda backspace icin ^H gordugumde, readPassword'den kaynaklandigini dusunerek, loginFlow'u atladim;

Java:
private record Session(String username, int clearance) {}
public static void testMenuLoop() throws IOException {
    Session session = new Session("charlie", 3);
    FileSystem app = new FileSystem();
    app.loadStoreIfPresent();
    app.menuLoop(session);
}

Burda input dogru calisinca, dondum debuggerla iceri dogru girmeye (readLine ve nextLine'in implementasyonlarina) basladim ve kiyasladim neyi farkli yapiyorlar bu kadar diye. En son da JLine'i bulunca, JLine ne zamandan beri default arastirdim, opt-out icin gereken secenegi buldum ayni sayfada. Belki yardimci olur diyerek kafamdan gecenlerin kisa bi ozetini yazayim dedim debugging kismi icin.
Kodu paylasmazsan yardimci olamayiz ki.
Koddan değildir diye atmamıştım, koddan olsa Terminal'de de problem yaşardım diye ancak buyrun aşağı bıraktım.

Denediğim şeyler:
-Scanner sınıfını input bug yapmasın diye tek çatıda toplamak.
-Nizami Buffer
-Print - Println kontrolleri
-Avare println - \n bırakmak
-Her output sonrası System.out.flush();

Java:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.*;
import java.util.*;


public class FileSystem {
    private static final Path SALT_PATH = Paths.get("salt.txt");
    private static final Path SHADOW_PATH = Paths.get("shadow.txt");
    private static final Path STORE_PATH = Paths.get("Files.store");

    private static class VFile {
        String name;
        String owner;
        int level;          // classification
        StringBuilder data; // content

        VFile(String name, String owner, int level) {
            this.name = name;
            this.owner = owner;
            this.level = level;
            this.data = new StringBuilder();
        }

        String serialize() {
            // name|owner|level|content (content escaped as base64 to remain readable/safe)
            String contentB64 = Base64.getEncoder().encodeToString(data.toString().getBytes(StandardCharsets.UTF_8));
            return name + "|" + owner + "|" + level + "|" + contentB64;
        }

        static VFile deserialize(String line) {
            String[] parts = line.split("\\|", 4);
            if (parts.length < 4) return null;
            VFile f = new VFile(parts[0], parts[1], Integer.parseInt(parts[2]));
            try {
                byte[] bytes = Base64.getDecoder().decode(parts[3]);
                f.data.append(new String(bytes, StandardCharsets.UTF_8));
            } catch (IllegalArgumentException e) {
                // ignore decode errors; leave content empty
            }
            return f;
        }
    }

    private final Map<String, VFile> files = new LinkedHashMap<>(); // preserve insertion order

    private static class Session {
        String username;
        int clearance;
    }

    public static void main(String[] args) throws Exception {
        // Always print MD5 test first
        String testInput = "This is a test";
        System.out.println("MD5 (\"" + testInput + "\") = " + md5Hex(testInput));

        FileSystem app = new FileSystem();
        if (args.length == 1 && "-i".equals(args[0])) {
            app.initialiseUser();
            return;
        }
        app.loadStoreIfPresent();
        Session s = app.loginFlow();
        if (s != null) {
            app.menuLoop(s);
        }
    }

    // UserReg
    private void initialiseUser() throws IOException, NoSuchAlgorithmException {
        ensureFilesExist();
        Scanner sc = new Scanner(System.in);
        System.out.print("Username: ");
        String username = sc.nextLine().trim();
        if (username.isEmpty() || username.contains(":")) {
            System.out.println("Invalid username.");
            return;
        }
        Map<String, String> saltMap = readSaltFile();
        if (saltMap.containsKey(username)) {
            System.out.println("User already exists. Aborting.");
            return;
        }
        String password = promptPassword(sc, "Password: ");
        String confirm = promptPassword(sc, "Confirm Password: ");
        if (!password.equals(confirm)) {
            System.out.println("Passwords do not match. Aborting.");
            return;
        }
        List<String> pwIssues = passwordIssues(password);
        if (!pwIssues.isEmpty()) {
            System.out.println("Password does not meet requirements:");
            for (String issue : pwIssues) System.out.println(" - " + issue);
            System.out.println("Requirements: at least 8 chars, include lowercase, uppercase, and digit.");
            return;
        }
        int clearance = promptClearance(sc);
        if (clearance < 0) return;

        String salt = randomEightDigits();
        String passSaltHash = md5Hex(password + salt);

        // Write salt.txt line
        String saltLine = username + ":" + salt + System.lineSeparator();
        Files.write(SALT_PATH, saltLine.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND);

        // Write shadow.txt line
        String shadowLine = username + ":" + passSaltHash + ":" + clearance + System.lineSeparator();
        Files.write(SHADOW_PATH, shadowLine.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND);

        System.out.println("User '" + username + "' created with clearance " + clearance + ".");
        System.out.println("Added to salt.txt and shadow.txt.");
    }

    //Login

    private Session loginFlow() throws Exception {
        ensureFilesExist();
        Scanner sc = new Scanner(System.in);
        System.out.print("Username: ");
        String username = sc.nextLine().trim();
        String password = promptPassword(sc, "Password: ");

        Map<String, String> saltMap = readSaltFile();
        if (!saltMap.containsKey(username)) {
            System.out.println("Username not found in salt.txt. Exiting.");
            return null;
        }
        String salt = saltMap.get(username);
        System.out.println(username + " found in salt.txt");
        System.out.println("salt retrieved: " + salt);
        System.out.println("hashing ...");
        String hash = md5Hex(password + salt);
        System.out.println("hash value: " + hash);

        // Files
        ShadowEntry sh = readShadowFor(username);
        if (sh == null) {
            System.out.println("No matching entry in shadow.txt. Exiting.");
            return null;
        }
        if (!hash.equalsIgnoreCase(sh.passSaltHash)) {
            System.out.println("Authentication failed: hash mismatch.");
            return null;
        }
        Session s = new Session();
        s.username = username;
        s.clearance = sh.clearance;
        System.out.println("Authentication for user " + username + " complete.");
        System.out.println("The clearance for " + username + " is " + s.clearance + ".");
        return s;
    }

    private void menuLoop(Session s) throws IOException {
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.flush();
            System.out.println(); // blank line for readability
            System.out.println("Options: (C)reate, (A)ppend, (R)ead, (W)rite, (L)ist, (S)ave or (E)xit.");
            System.out.print("Your choice: ");
            System.out.flush();
            String choice = sc.nextLine().trim();
            if (choice.isEmpty()) continue;
            char k = Character.toUpperCase(choice.charAt(0));
            switch (k) {
                case 'C':
                    System.out.print("Filename: ");
                    String newName = sc.nextLine().trim();
                    if (newName.isEmpty()) {
                        System.out.println("Invalid name.");
                        break;
                    }
                    if (files.containsKey(newName)) {
                        System.out.println("File already exists.");
                    } else {
                        VFile f = new VFile(newName, s.username, s.clearance);
                        files.put(newName, f);
                        System.out.println("File '" + newName + "' created at level " + f.level + ".");
                    }
                    break;
                case 'A':
                    handleAppend(sc, s);
                    break;
                case 'R':
                    handleRead(sc, s);
                    break;
                case 'W':
                    handleWrite(sc, s, /*overwrite*/true);
                    break;
                case 'L':
                    listFiles();
                    break;
                case 'S':
                    saveStore();
                    System.out.println("Saved to " + STORE_PATH.getFileName() + ".");
                    break;
                case 'E':
                    System.out.print("Shut down the FileSystem? (Y)es or (N)o ");
                    System.out.flush();
                    String yn = sc.nextLine().trim();
                    if (!yn.isEmpty() && Character.toUpperCase(yn.charAt(0)) == 'Y') {
                        System.out.println("Goodbye.");
                        return;
                    }
                    break;
                default:
                    System.out.println("Unknown option.");
            }
        }
    }



    private void handleAppend(Scanner sc, Session s) {
        System.out.print("Filename: ");
        System.out.flush();
        String name = sc.nextLine().trim();
        VFile f = files.get(name);
        if (f == null) { System.out.println("No such file."); return; }
        if (!canWriteOrAppend(s.clearance, f.level)) {
            System.out.println("Access denied (no write down). Your level: " + s.clearance + ", file level: " + f.level);
            return;
        }
        System.out.print("Append text (single line): ");
        String line = sc.nextLine();
        f.data.append(line).append(System.lineSeparator());
        System.out.println("Appended.");
    }

    private void handleWrite(Scanner sc, Session s, boolean overwrite) {
        System.out.print("Filename: ");
        System.out.flush();
        String name = sc.nextLine().trim();
        VFile f = files.get(name);
        if (f == null) { System.out.println("No such file."); return; }
        if (!canWriteOrAppend(s.clearance, f.level)) {
            System.out.println("Access denied (no write down). Your level: " + s.clearance + ", file level: " + f.level);
            return;
        }
        System.out.println("Enter content; end with a single line containing only " + ":wq");
        StringBuilder buf = new StringBuilder();
        while (true) {
            String line = sc.nextLine();
            if (line.equals(":wq")) break;
            buf.append(line).append(System.lineSeparator());
        }
        if (overwrite) f.data.setLength(0);
        f.data.append(buf);
        System.out.println("Written.");
    }

    private void handleRead(Scanner sc, Session s) {
        System.out.print("Filename: ");
        System.out.flush();
        String name = sc.nextLine().trim();
        VFile f = files.get(name);
        if (f == null) { System.out.println("No such file."); return; }
        if (!canRead(s.clearance, f.level)) {
            System.out.println("Access denied (no read up). Your level: " + s.clearance + ", file level: " + f.level);
            return;
        }
        System.out.println("--- BEGIN " + name + " (owner=" + f.owner + ", level=" + f.level + ") ---");
        System.out.print(f.data.toString());
        System.out.println("--- END " + name + " ---");
    }

    private void listFiles() {
        if (files.isEmpty()) {
            System.out.println("No files in system.");
            return;
        }
        System.out.println("Name\tOwner\tLevel\tBytes");
        for (VFile f : files.values()) {
            System.out.println(f.name + "\t" + f.owner + "\t" + f.level + "\t" + f.data.length());
        }
    }

    // higher number dominates lower number.
    private static boolean canRead(int subjectClearance, int objectLevel) {
        return subjectClearance >= objectLevel; // no read up violation
    }

    private static boolean canWriteOrAppend(int subjectClearance, int objectLevel) {
        return subjectClearance <= objectLevel; // no write down violation
    }

    // ===== Persistence for Files.store =====
    private void saveStore() throws IOException {
        try (BufferedWriter bw = Files.newBufferedWriter(STORE_PATH, StandardCharsets.UTF_8)) {
            bw.write("# Files.store — human-readable file records\n");
            bw.write("# Format: name|owner|level|base64(content)\n");
            for (VFile f : files.values()) {
                bw.write(f.serialize());
                bw.write('\n');
            }
        }
    }

    private void loadStoreIfPresent() throws IOException {
        if (!Files.exists(STORE_PATH)) return;
        List<String> lines = Files.readAllLines(STORE_PATH, StandardCharsets.UTF_8);
        int count = 0;
        for (String line : lines) {
            if (line.startsWith("#") || line.isBlank()) continue;
            VFile f = VFile.deserialize(line);
            if (f != null) {
                files.put(f.name, f);
                count++;
            }
        }
        System.out.println("Loaded " + count + " file record(s) from " + STORE_PATH.getFileName() + ".");
    }

    private static class ShadowEntry {
        String user;
        String passSaltHash;
        int clearance;
    }

    private static Map<String, String> readSaltFile() throws IOException {
        Map<String, String> map = new HashMap<>();
        if (!Files.exists(SALT_PATH)) return map;
        List<String> lines = Files.readAllLines(SALT_PATH, StandardCharsets.UTF_8);
        for (String line : lines) {
            if (line.isBlank()) continue;
            String[] parts = line.split(":", 2);
            if (parts.length == 2) map.put(parts[0], parts[1]);
        }
        return map;
    }

    private static ShadowEntry readShadowFor(String username) throws IOException {
        if (!Files.exists(SHADOW_PATH)) return null;
        List<String> lines = Files.readAllLines(SHADOW_PATH, StandardCharsets.UTF_8);
        for (String line : lines) {
            if (line.isBlank()) continue;
            String[] parts = line.split(":", 3);
            if (parts.length == 3 && parts[0].equals(username)) {
                ShadowEntry e = new ShadowEntry();
                e.user = parts[0];
                e.passSaltHash = parts[1];
                try { e.clearance = Integer.parseInt(parts[2]); } catch (NumberFormatException ex) { return null; }
                return e;
            }
        }
        return null;
    }

    private static void ensureFilesExist() throws IOException {
        if (!Files.exists(SALT_PATH)) Files.createFile(SALT_PATH);
        if (!Files.exists(SHADOW_PATH)) Files.createFile(SHADOW_PATH);
    }

    private static String md5Hex(String s) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] dig = md.digest(s.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : dig) sb.append(String.format("%02x", b));
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    private static String promptPassword(Scanner sc, String prompt) {
        Console console = System.console();
        if (console != null) {
            char[] pwd = console.readPassword(prompt);
            return pwd == null ? "" : new String(pwd);
        }
        System.out.print(prompt);
        return sc.nextLine();
    }

    private static List<String> passwordIssues(String pw) {
        List<String> issues = new ArrayList<>();
        if (pw.length() < 8) issues.add("must be at least 8 characters long");
        if (!pw.chars().anyMatch(Character::isLowerCase)) issues.add("must include a lowercase letter");
        if (!pw.chars().anyMatch(Character::isUpperCase)) issues.add("must include an uppercase letter");
        if (!pw.chars().anyMatch(Character::isDigit)) issues.add("must include a digit");
        return issues;
    }

    private static int promptClearance(Scanner sc) {
        System.out.print("User clearance (0 or 1 or 2 or 3): ");
        String in = sc.nextLine().trim();
        try {
            int level = Integer.parseInt(in);
            if (level < 0 || level > 3) throw new NumberFormatException();
            return level;
        } catch (NumberFormatException e) {
            System.out.println("Invalid clearance. Must be 0, 1, 2, or 3.");
            return -1;
        }
    }

    private static String randomEightDigits() {
        SecureRandom rnd = new SecureRandom();
        int n = rnd.nextInt(1_0000_0000); // 0..99,999,999
        return String.format("%08d", n);
    }
}
 
Tam olarak CMD çıktılarındaki hatayı anlayamadım ama - tabii ki farklı çıktılar olduğu bariz belli - bir de WSL üzerinde dener misiniz? İkisi de Linux tabanlı olduğu için muhtemelen aynı çıktıları alırsınız diye düşünüyorum. Ek olarak CMD çıktılarında dosya oluşturulmuş gibi gözüküyor, yani create ve yes olarak input vermişsiniz ve program hatasız bitmiş gibi, değil mi?
 
Tam olarak CMD çıktılarındaki hatayı anlayamadım ama - tabii ki farklı çıktılar olduğu bariz belli - bir de WSL üzerinde dener misiniz? İkisi de Linux tabanlı olduğu için muhtemelen aynı çıktıları alırsınız diye düşünüyorum. Ek olarak CMD çıktılarında dosya oluşturulmuş gibi gözüküyor, yani create ve yes olarak input vermişsiniz ve program hatasız bitmiş gibi, değil mi?
Fonksiyonlarda sıkıntı yok zaten ancak ezbere bildiğim için sadece ben kullanabiliyorum. Yazılar hep iç içe geçiyor. Mesela Terminal tıkır tıkır yürürken CMD’de tekrar tekrar boş yere Enter’a basmam gerekiyor tek satır geriden geliyor gibi her şey ve böyle kullanmak imkansız bir hal alıyor bir yerden sonra.

Create demişim, sonra her şey birbirine girmiş E demişim çıkmak için sonra Y demişim, Goodbye, Y’den önce geliyor. Create’den sonra da Exit gelmiş. Hepsinin yeri karışık. 🥴

Kodun üzerinde 6 saat uğraştım zannetmiyorum ki hata orada olsun ya Windows sıkıntılı ya benim Windows’um sıkıntılı. WSL deneyeceğim bu arada.
 
Son düzenleme:
Ya Scanner kullan ya Console. Ikisini birden kullanma. System.console().readLine() yaptigindan itibaren Windows'un terminal input modu degisiyor. Scanner dogru bi sekilde line editleri takip edemez hale geliyor. Console yerine sadece Scanner kullandigimda okumalar bozulmuyor. Sadece console kullandigimda da bozulmuyor. Ikisini birden kullandiginda bozuluyor.

Bunun sebebi Java, default olarak JLine kullaniyor. JLine napiyorda Scanner'i bozuyor diyorsan, inan bi fikrim yok ama bozuyor. Eger programi; -Djdk.console=java.base VM secenegiyle baslatirsan duzgun calisiyor.

1757356916622.webp


Yerinde olsam ikisini de kullanmam, direkt JLine kullanirim. Daha gelismis bir arac. Oto tamamlama, gecmis vs ozellikleri de sunacak sana. Platform belirsizliginden kurtulmus olurum. Daha iyi toollar olur elimde. Merak ediyorsan kendin oturup JLine.readLine yaparken ne yapiyor inceleyebilirsin. Kodu bayagi uzundu. Usendim okumaya.

Not: Bahsi gecen her seyi JDK 24 ile test ettim. Daha onceki versiyonlar icin dogru olmayabilir. JDK 22'den beri JLine default haline gelmis.

Asagidaki kodun bulundugu paket; package jdk.internal.org.jline.reader.impl; (GitHub)

Java:
public String readLine(String prompt, String rightPrompt, MaskingCallback maskingCallback, String buffer)
            throws UserInterruptException, EndOfFileException {
        // prompt may be null
        // maskingCallback may be null
        // buffer may be null
        if (!commandsBuffer.isEmpty()) {
            String cmd = commandsBuffer.remove(0);
            boolean done = false;
            do {
                try {
                    parser.parse(cmd, cmd.length() + 1, ParseContext.ACCEPT_LINE);
                    done = true;
                } catch (EOFError e) {
                    if (commandsBuffer.isEmpty()) {
                        throw new IllegalArgumentException("Incompleted command: \n" + cmd);
                    }
                    cmd += "\n";
                    cmd += commandsBuffer.remove(0);
                } catch (SyntaxError e) {
                    done = true;
                } catch (Exception e) {
                    commandsBuffer.clear();
                    throw new IllegalArgumentException(e.getMessage());
                }
            } while (!done);
            AttributedStringBuilder sb = new AttributedStringBuilder();
            sb.styled(AttributedStyle::bold, cmd);
            sb.toAttributedString().println(terminal);
            terminal.flush();
            return finish(cmd);
        }

        if (!startedReading.compareAndSet(false, true)) {
            throw new IllegalStateException();
        }

        Thread readLineThread = Thread.currentThread();
        SignalHandler previousIntrHandler = null;
        SignalHandler previousWinchHandler = null;
        SignalHandler previousContHandler = null;
        Attributes originalAttributes = null;
        boolean dumb = isTerminalDumb();
        try {

            this.maskingCallback = maskingCallback;

            /*
             * This is the accumulator for VI-mode repeat count. That is, while in
             * move mode, if you type 30x it will delete 30 characters. This is
             * where the "30" is accumulated until the command is struck.
             */
            repeatCount = 0;
            mult = 1;
            regionActive = RegionType.NONE;
            regionMark = -1;

            smallTerminalOffset = 0;

            state = State.NORMAL;

            modifiedHistory.clear();

            setPrompt(prompt);
            setRightPrompt(rightPrompt);
            buf.clear();
            if (buffer != null) {
                buf.write(buffer);
            }
            if (nextCommandFromHistory && nextHistoryId > 0) {
                if (history.size() > nextHistoryId) {
                    history.moveTo(nextHistoryId);
                } else {
                    history.moveTo(history.last());
                }
                buf.write(history.current());
            } else {
                nextHistoryId = -1;
            }
            nextCommandFromHistory = false;
            undo.clear();
            parsedLine = null;
            keyMap = MAIN;

            if (history != null) {
                history.attach(this);
            }

            try {
                lock.lock();

                this.reading = true;

                previousIntrHandler = terminal.handle(Signal.INT, signal -> readLineThread.interrupt());
                previousWinchHandler = terminal.handle(Signal.WINCH, this::handleSignal);
                previousContHandler = terminal.handle(Signal.CONT, this::handleSignal);
                originalAttributes = terminal.enterRawMode();

                doDisplay();

                // Move into application mode
                if (!dumb) {
                    terminal.puts(Capability.keypad_xmit);
                    if (isSet(Option.AUTO_FRESH_LINE)) callWidget(FRESH_LINE);
                    if (isSet(Option.MOUSE)) terminal.trackMouse(Terminal.MouseTracking.Normal);
                    if (isSet(Option.BRACKETED_PASTE)) terminal.writer().write(BRACKETED_PASTE_ON);
                }

                callWidget(CALLBACK_INIT);

                if (!isSet(Option.DISABLE_UNDO)) undo.newState(buf.copy());

                // Draw initial prompt
                redrawLine();
                redisplay();
            } finally {
                lock.unlock();
            }

            while (true) {

                KeyMap<Binding> local = null;
                if (isInViCmdMode() && regionActive != RegionType.NONE) {
                    local = keyMaps.get(VISUAL);
                }
                Binding o = readBinding(getKeys(), local);
                if (o == null) {
                    throw new EndOfFileException().partialLine(buf.length() > 0 ? buf.toString() : null);
                }
                Log.trace("Binding: ", o);
                if (buf.length() == 0
                        && getLastBinding().charAt(0) == originalAttributes.getControlChar(ControlChar.VEOF)) {
                    throw new EndOfFileException();
                }

                // If this is still false after handling the binding, then
                // we reset our repeatCount to 0.
                isArgDigit = false;
                // Every command that can be repeated a specified number
                // of times, needs to know how many times to repeat, so
                // we figure that out here.
                count = ((repeatCount == 0) ? 1 : repeatCount) * mult;
                // Reset undo/redo flag
                isUndo = false;
                // Reset region after a paste
                if (regionActive == RegionType.PASTE) {
                    regionActive = RegionType.NONE;
                }

                try {
                    lock.lock();
                    // Get executable widget
                    Buffer copy = buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE)
                            ? buf.copy()
                            : null;
                    Widget w = getWidget(o);
                    if (!w.apply()) {
                        beep();
                    }
                    if (!isSet(Option.DISABLE_UNDO)
                            && !isUndo
                            && copy != null
                            && buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE)
                            && !copy.toString().equals(buf.toString())) {
                        undo.newState(buf.copy());
                    }

                    switch (state) {
                        case DONE:
                            return finishBuffer();
                        case IGNORE:
                            return "";
                        case EOF:
                            throw new EndOfFileException();
                        case INTERRUPT:
                            throw new UserInterruptException(buf.toString());
                    }

                    if (!isArgDigit) {
                        /*
                         * If the operation performed wasn't a vi argument
                         * digit, then clear out the current repeatCount;
                         */
                        repeatCount = 0;
                        mult = 1;
                    }

                    if (!dumb) {
                        redisplay();
                    }
                } finally {
                    lock.unlock();
                }
            }
        } catch (IOError e) {
            if (e.getCause() instanceof InterruptedIOException) {
                throw new UserInterruptException(buf.toString());
            } else {
                throw e;
            }
        } finally {
            AtomicBoolean interrupted = new AtomicBoolean(Thread.interrupted());
            try {
                lock.lock();

                this.reading = false;

                Terminal.SignalHandler tmpHandler = terminal.handle(Signal.INT, s -> interrupted.set(true));
                if (previousIntrHandler == null) {
                    previousIntrHandler = tmpHandler;
                }

                cleanup();
                if (originalAttributes != null) {
                    terminal.setAttributes(originalAttributes);
                }
                if (previousIntrHandler != null) {
                    terminal.handle(Signal.INT, previousIntrHandler);
                }
                if (previousWinchHandler != null) {
                    terminal.handle(Signal.WINCH, previousWinchHandler);
                }
                if (previousContHandler != null) {
                    terminal.handle(Signal.CONT, previousContHandler);
                }
            } finally {
                lock.unlock();
                startedReading.set(false);
                if (interrupted.get()) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
 
Son düzenleyen: Moderatör:
Ya Scanner kullan ya Console. Ikisini birden kullanma. System.console().readLine() yaptigindan itibaren Windows'un terminal input modu degisiyor. Scanner dogru bi sekilde line editleri takip edemez hale geliyor. Console yerine sadece Scanner kullandigimda okumalar bozulmuyor. Sadece console kullandigimda da bozulmuyor. Ikisini birden kullandiginda bozuluyor.

Bunun sebebi Windows'ta Java, default olarak JLine kullaniyor. JLine napiyorda Scanner'i bozuyor diyorsan, inan bi fikrim yok ama bozuyor. Eger programi; -Djdk.console=java.base VM secenegiyle baslatirsan duzgun calisiyor.
Eki Görüntüle 183341

Yerinde olsam ikisini de kullanmam, direkt JLine kullanirim. Daha gelismis bir arac. Oto tamamlama, gecmis vs ozellikleri de sunacak sana. Platform belirsizliginden kurtulmus olurum. Daha iyi toollar olur elimde. Merak ediyorsan kendin oturup JLine.readLine yaparken ne yapiyor inceleyebilirsin. Kodu bayagi uzundu. Usendim okumaya.

Not: Bahsi gecen her seyi JDK 24 ile test ettim. Daha onceki versiyonlar icin dogru olmayabilir. JDK 22'den beri JLine default haline gelmis.

Asagidaki kodun bulundugu paket; package jdk.internal.org.jline.reader.impl; (GitHub)
Java:
public String readLine(String prompt, String rightPrompt, MaskingCallback maskingCallback, String buffer)
            throws UserInterruptException, EndOfFileException {
        // prompt may be null
        // maskingCallback may be null
        // buffer may be null
        if (!commandsBuffer.isEmpty()) {
            String cmd = commandsBuffer.remove(0);
            boolean done = false;
            do {
                try {
                    parser.parse(cmd, cmd.length() + 1, ParseContext.ACCEPT_LINE);
                    done = true;
                } catch (EOFError e) {
                    if (commandsBuffer.isEmpty()) {
                        throw new IllegalArgumentException("Incompleted command: \n" + cmd);
                    }
                    cmd += "\n";
                    cmd += commandsBuffer.remove(0);
                } catch (SyntaxError e) {
                    done = true;
                } catch (Exception e) {
                    commandsBuffer.clear();
                    throw new IllegalArgumentException(e.getMessage());
                }
            } while (!done);
            AttributedStringBuilder sb = new AttributedStringBuilder();
            sb.styled(AttributedStyle::bold, cmd);
            sb.toAttributedString().println(terminal);
            terminal.flush();
            return finish(cmd);
        }

        if (!startedReading.compareAndSet(false, true)) {
            throw new IllegalStateException();
        }

        Thread readLineThread = Thread.currentThread();
        SignalHandler previousIntrHandler = null;
        SignalHandler previousWinchHandler = null;
        SignalHandler previousContHandler = null;
        Attributes originalAttributes = null;
        boolean dumb = isTerminalDumb();
        try {

            this.maskingCallback = maskingCallback;

            /*
             * This is the accumulator for VI-mode repeat count. That is, while in
             * move mode, if you type 30x it will delete 30 characters. This is
             * where the "30" is accumulated until the command is struck.
             */
            repeatCount = 0;
            mult = 1;
            regionActive = RegionType.NONE;
            regionMark = -1;

            smallTerminalOffset = 0;

            state = State.NORMAL;

            modifiedHistory.clear();

            setPrompt(prompt);
            setRightPrompt(rightPrompt);
            buf.clear();
            if (buffer != null) {
                buf.write(buffer);
            }
            if (nextCommandFromHistory && nextHistoryId > 0) {
                if (history.size() > nextHistoryId) {
                    history.moveTo(nextHistoryId);
                } else {
                    history.moveTo(history.last());
                }
                buf.write(history.current());
            } else {
                nextHistoryId = -1;
            }
            nextCommandFromHistory = false;
            undo.clear();
            parsedLine = null;
            keyMap = MAIN;

            if (history != null) {
                history.attach(this);
            }

            try {
                lock.lock();

                this.reading = true;

                previousIntrHandler = terminal.handle(Signal.INT, signal -> readLineThread.interrupt());
                previousWinchHandler = terminal.handle(Signal.WINCH, this::handleSignal);
                previousContHandler = terminal.handle(Signal.CONT, this::handleSignal);
                originalAttributes = terminal.enterRawMode();

                doDisplay();

                // Move into application mode
                if (!dumb) {
                    terminal.puts(Capability.keypad_xmit);
                    if (isSet(Option.AUTO_FRESH_LINE)) callWidget(FRESH_LINE);
                    if (isSet(Option.MOUSE)) terminal.trackMouse(Terminal.MouseTracking.Normal);
                    if (isSet(Option.BRACKETED_PASTE)) terminal.writer().write(BRACKETED_PASTE_ON);
                }

                callWidget(CALLBACK_INIT);

                if (!isSet(Option.DISABLE_UNDO)) undo.newState(buf.copy());

                // Draw initial prompt
                redrawLine();
                redisplay();
            } finally {
                lock.unlock();
            }

            while (true) {

                KeyMap<Binding> local = null;
                if (isInViCmdMode() && regionActive != RegionType.NONE) {
                    local = keyMaps.get(VISUAL);
                }
                Binding o = readBinding(getKeys(), local);
                if (o == null) {
                    throw new EndOfFileException().partialLine(buf.length() > 0 ? buf.toString() : null);
                }
                Log.trace("Binding: ", o);
                if (buf.length() == 0
                        && getLastBinding().charAt(0) == originalAttributes.getControlChar(ControlChar.VEOF)) {
                    throw new EndOfFileException();
                }

                // If this is still false after handling the binding, then
                // we reset our repeatCount to 0.
                isArgDigit = false;
                // Every command that can be repeated a specified number
                // of times, needs to know how many times to repeat, so
                // we figure that out here.
                count = ((repeatCount == 0) ? 1 : repeatCount) * mult;
                // Reset undo/redo flag
                isUndo = false;
                // Reset region after a paste
                if (regionActive == RegionType.PASTE) {
                    regionActive = RegionType.NONE;
                }

                try {
                    lock.lock();
                    // Get executable widget
                    Buffer copy = buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE)
                            ? buf.copy()
                            : null;
                    Widget w = getWidget(o);
                    if (!w.apply()) {
                        beep();
                    }
                    if (!isSet(Option.DISABLE_UNDO)
                            && !isUndo
                            && copy != null
                            && buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE)
                            && !copy.toString().equals(buf.toString())) {
                        undo.newState(buf.copy());
                    }

                    switch (state) {
                        case DONE:
                            return finishBuffer();
                        case IGNORE:
                            return "";
                        case EOF:
                            throw new EndOfFileException();
                        case INTERRUPT:
                            throw new UserInterruptException(buf.toString());
                    }

                    if (!isArgDigit) {
                        /*
                         * If the operation performed wasn't a vi argument
                         * digit, then clear out the current repeatCount;
                         */
                        repeatCount = 0;
                        mult = 1;
                    }

                    if (!dumb) {
                        redisplay();
                    }
                } finally {
                    lock.unlock();
                }
            }
        } catch (IOError e) {
            if (e.getCause() instanceof InterruptedIOException) {
                throw new UserInterruptException(buf.toString());
            } else {
                throw e;
            }
        } finally {
            AtomicBoolean interrupted = new AtomicBoolean(Thread.interrupted());
            try {
                lock.lock();

                this.reading = false;

                Terminal.SignalHandler tmpHandler = terminal.handle(Signal.INT, s -> interrupted.set(true));
                if (previousIntrHandler == null) {
                    previousIntrHandler = tmpHandler;
                }

                cleanup();
                if (originalAttributes != null) {
                    terminal.setAttributes(originalAttributes);
                }
                if (previousIntrHandler != null) {
                    terminal.handle(Signal.INT, previousIntrHandler);
                }
                if (previousWinchHandler != null) {
                    terminal.handle(Signal.WINCH, previousWinchHandler);
                }
                if (previousContHandler != null) {
                    terminal.handle(Signal.CONT, previousContHandler);
                }
            } finally {
                lock.unlock();
                startedReading.set(false);
                if (interrupted.get()) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
Çok teşekkürler hocam yeni bir şey kattın.
 
Çok teşekkürler hocam yeni bir şey kattın.
Rica ederim. Sorunu nasil tespit ettigime dair sureci anlatacak olursam;
Username girerken, girilen metni editleyebilirken, menuLoop icerisinde girilen metni editlemeye kalktigimda backspace icin ^H gordugumde, readPassword'den kaynaklandigini dusunerek, loginFlow'u atladim;

Java:
private record Session(String username, int clearance) {}
public static void testMenuLoop() throws IOException {
    Session session = new Session("charlie", 3);
    FileSystem app = new FileSystem();
    app.loadStoreIfPresent();
    app.menuLoop(session);
}

Burda input dogru calisinca, dondum debuggerla iceri dogru girmeye (readLine ve nextLine'in implementasyonlarina) basladim ve kiyasladim neyi farkli yapiyorlar bu kadar diye. En son da JLine'i bulunca, JLine ne zamandan beri default arastirdim, opt-out icin gereken secenegi buldum ayni sayfada. Belki yardimci olur diyerek kafamdan gecenlerin kisa bi ozetini yazayim dedim debugging kismi icin.
 
Son düzenleyen: Moderatör:
Çözüm
Rica ederim. Sorunu nasil tespit ettigime dair sureci anlatacak olursam;
Username girerken, girilen metni editleyebilirken, menuLoop icerisinde girilen metni editlemeye kalktigimda backspace icin ^H gordugumde, readPassword'den kaynaklandigini dusunerek, loginFlow'u atladim;
Hocam çok makbule geçti, gidiş yolunu verdiğiniz için de teşekkür ederim, bu bilgiyi kullanacağım. Geçen senelerde yeşil forumda yine bir Java problemimi çözmüştünüz diye hatırlıyorum. :)
 
Hocam çok makbule geçti, gidiş yolunu verdiğiniz için de teşekkür ederim, bu bilgiyi kullanacağım. Geçen senelerde yeşil forumda yine bir Java problemimi çözmüştünüz diye hatırlıyorum. :)
Dogrudur, olabilir. Orda en cok mesaj attigim kategori Programlama kategorisiydi.