public class KontrasParameter {

    public static void main(String[] args) {
        System.out.println("== Pola rawan: penggabungan teks ==");
        tampilRawan("Budi");
        tampilRawan("' OR '1'='1");

        System.out.println();
        System.out.println("== Pola aman: placeholder ? + parameter terikat ==");
        KueriBerparameter aman = new KueriBerparameter(
                "SELECT * FROM pengguna WHERE nama = ?");
        aman.setString(1, "' OR '1'='1");
        System.out.println("template         : " + aman.templateDasar());
        System.out.println("parameter 1      : " + aman.nilaiParameter(1)
                + " (dikirim terpisah, bukan digabung)");
        System.out.println("ilustrasi render : " + aman.render());

        System.out.println();
        System.out.println("== Tipe parameter berbeda, setter berbeda ==");
        KueriBerparameter halaman = new KueriBerparameter(
                "SELECT * FROM pengguna WHERE kota = ? LIMIT ?");
        halaman.setString(1, "Jakarta");
        halaman.setInt(2, 10);
        System.out.println("ilustrasi render : " + halaman.render());

        System.out.println();
        System.out.println("== Indeks parameter berbasis 1 ==");
        try {
            halaman.setString(3, "Solo");
        } catch (IllegalArgumentException e) {
            System.out.println("DITOLAK: " + e.getMessage());
        }
    }

    /** Pola yang HARUS dihindari: nilai pengguna ditempel langsung ke SQL. */
    static void tampilRawan(String input) {
        String sql = "SELECT * FROM pengguna WHERE nama = '" + input + "'";
        System.out.println("input : " + input);
        System.out.println("SQL   : " + sql);
        if (input.contains("'")) {
            System.out.println("catatan: tanda kutip menutup literal SQL,"
                    + " kondisi WHERE bisa diubah pengguna");
        }
        System.out.println();
    }
}

/** Meniru perilaku PreparedStatement: template tetap, nilai terikat terpisah. */
class KueriBerparameter {

    private final String template;
    private final String[] nilai;
    private final String[] tipe;

    KueriBerparameter(String template) {
        int jumlah = template.length() - template.replace("?", "").length();
        this.template = template;
        this.nilai = new String[jumlah];
        this.tipe = new String[jumlah];
    }

    void setString(int indeks, String v) {
        simpan(indeks, v, "String");
    }

    void setInt(int indeks, int v) {
        simpan(indeks, String.valueOf(v), "int");
    }

    private void simpan(int indeks, String v, String jenis) {
        if (indeks < 1 || indeks > nilai.length) {
            throw new IllegalArgumentException(
                    "indeks parameter harus 1.." + nilai.length
                            + ", diterima: " + indeks);
        }
        nilai[indeks - 1] = v;
        tipe[indeks - 1] = jenis;
    }

    String templateDasar() {
        return template;
    }

    String nilaiParameter(int indeks) {
        return nilai[indeks - 1];
    }

    /** Ilustrasi saja: driver sungguhan mengirim nilai terpisah dari template. */
    String render() {
        StringBuilder sql = new StringBuilder();
        int param = 0;
        for (char c : template.toCharArray()) {
            if (c == '?') {
                param++;
                if (nilai[param - 1] == null) {
                    throw new IllegalStateException(
                            "parameter " + param + " belum diikat");
                }
                if ("int".equals(tipe[param - 1])) {
                    sql.append(nilai[param - 1]);
                } else {
                    sql.append('\'')
                            .append(nilai[param - 1].replace("'", "''"))
                            .append('\'');
                }
            } else {
                sql.append(c);
            }
        }
        return sql.toString();
    }
}
