6
\$\begingroup\$

Background

About the Fleissner-Grille

The Fleissner-Grille is a really easy possibility to encrypt text. You just have to take a grid of size n*n with (n*n)/4 holes (example). You then just write one letter in one hole. After you've filled every hole with a letter, you just turn the grille by 90° degrees and start again. One requirement that this works is that the grille is not symmetric. Otherwise you would overwrite letters.

To create such a grille, you can just fill one quarter of the grille with the number 1-4. Then you turn this quarter by 90° degrees and apply the formula $$z = z \% 4 + 1$$ to every number. An example on how that works is available here.[1]

About this project

The basis of this project is already covered here, but I changed a lot since I asked this question:

  • Added GUI: The last version of this project was just running in the terminal. I now added the following GUI:

    enter image description here

    enter image description here

  • Changed codestructure: In the last version, I just worked through the problem in a very procedural way. I now tried to structure my code in a better way: I used classes and more methods.

  • The grille-size is no longer fixed: The longer the entered text is, the bigger the grille is.


Code

Control.java

package Fleissner;

import javax.swing.SwingUtilities;

public class Control {

  // Just to start GUI

  public static void main(String[] args) {
    SwingUtilities.invokeLater(Gui::new);
  }
}

Fleissner.java

package Fleissner;

import java.lang.Math;          // needed for sqrt()

public class Fleissner {
  private int grilleSize;       // size of the grille: if the grille has a size of 6x6, this variable
                                // equals to 36.

  private int substringSize;    // grilleSize / 4
  private int sizeSqrt;         // sqrt(grilleSize)
  private String text;          // text that will be encrypted
  private char[][] out;         // output
  private int[][] grille;       // The turning grille

  public Fleissner(String text) {
    this.text = text;

    // determine needed grille-size
    int length = text.length();
    if (length == 0) {
      length++;
    }
    while (true) {

      // grilles with size > 64 cannot be displayed
      if (length > 64) {
        grilleSize = 64;
        substringSize = grilleSize / 4;
        sizeSqrt = (int) Math.sqrt((double) grilleSize);
        break;
      }

      // conditions for grille-size-numbers.
      // Possible are - for example - 2x2, 4x4, 6x6 and 8x8
      if (Math.sqrt((double) length) % 2 == 0 && length % 4 == 0) {
        grilleSize = length;
        substringSize = grilleSize / 4;
        sizeSqrt = (int) Math.sqrt((double) grilleSize);
        break;
      }
      length++;
    }
  }

  public String fleissnerEncryption() {

    // If text is too short for grille, the grille will be filled up with random chars ('a' - 'z')
    if (text.length() < grilleSize) {
      while (text.length() < grilleSize) {
        text = text + (char) ('a' + (int) (Math.random() * (('z' - 'a') + 1)));
      }
    }

    // Case that text is too long
    if (text.length() != grilleSize) {
      text = safeSubstring(text, 0, grilleSize);
    }

    out = encrypt();

    //Convert array "out" to String in desired format
    String result = "Encrypted:        ";
    int length = result.length();
    result += "|  Grille:\n";
    for (int i = 0; i < out.length; i++) {

      for (int j = 0; j < out[0].length; j++) {

        if (j == sizeSqrt) {
          for (int k = 0; k < length - 2 * sizeSqrt; k++) {
            result += " ";
          }
          result += "|  ";
        }
        result += out[i][j] + " ";
      }
      result += "\n";
    }

    return result;
  }

  private char[][] encrypt() {

    grille = creategrille();

    //The text now gets split up to substrings with length substringSize. 
    //Then the grille gets filled up with the chars.
    String subText = safeSubstring(text, 0, substringSize);

    int[] ar = {0 * substringSize, 1 * substringSize, 2 * substringSize, 3 * substringSize,
        4 * substringSize};

    out = new char[sizeSqrt][sizeSqrt * 2];

    int count = 0;
    while (count < grilleSize / substringSize) {
      int row = 0;
      int col = 0;
      int i = 0;

      //Filling grille with transposed chars
      while (i < subText.length()) {
        if (grille[row][col] == 1) {
          out[row][col] = subText.charAt(i);
          i = i + 1;
          if (col < sizeSqrt - 1) {
            col = col + 1;
          } else if (row < sizeSqrt - 1) {
            row = row + 1;
            col = 0;
          }
        } else if (col < sizeSqrt - 1) {
          col = col + 1;
        } else if (row < sizeSqrt - 1) {
          row = row + 1;
          col = 0;
        }
      }

      count = count + 1;
      int m = ar[count];
      int n = m + substringSize;
      subText = safeSubstring(text, m, n);
      grille = rotate(grille);
    }

    //Filling 2nd part of the array with the grille (needed for decrypting)
    for (int k = 0; k < out.length; k++) {
      for (int l = sizeSqrt; l < out[0].length; l++) {
        int radix = 10;
        out[k][l] = Character.forDigit(grille[k][l - sizeSqrt], radix);
      }
    }

    return out;
  }

  // This method creates a random grille
  private int[][] creategrille() {
    final int n = sizeSqrt / 2;
    int[][] a2 = new int[n][n];
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < n; ++j) {
        a2[i][j] = (int) ((Math.random()) * 4 + 1);
      }
    }
    int[][] a3 = getMatrix(a2);
    int[][] a4 = getMatrix(a3);
    int[][] a5 = getMatrix(a4);

    int[][] result = new int[sizeSqrt][sizeSqrt];

    /*
     * Filling result-array like this:
     *       [ (a2), (a3) ]
     *       [ (a5), (a4) ]
     */
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        result[i][j] = a2[i][j];
        result[i + n][j] = a5[i][j];
        result[i][j + n] = a3[i][j];
        result[i + n][j + n] = a4[i][j];
      }
    }

    return result;
  }

  //Rotates the matrix and applies formula
  private int[][] getMatrix(int[][] a2) {
    int[][] result = rotate(a2);
    for (int i = 0; i < result.length; i++) {
      for (int j = 0; j < result.length; j++) {
        result[i][j] = result[i][j] % 4 + 1;
      }
    }
    return result;
  }

  // This method rotates the "grille" clockwise 45 degrees
  private int[][] rotate(int[][] a1) {
    int n = a1.length;
    int[][] rotated = new int[n][n];
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < n; ++j) {
        rotated[i][j] = a1[n - j - 1][i];
      }
    }
    return rotated;
  }

  // Method to divide String into smaller substrings.
  private String safeSubstring(String str, int start, int end) {
    String out = "";
    if (end > str.length() - 1) {
      end = str.length();
    }
    while (start < end) {
      out = out + str.charAt(start);
      start = start + 1;

    }
    return out;
  }
}

Gui.java

/* Attribution:
 * Question by shareef(https://stackoverflow.com/users/944593/shareef):
 * https://stackoverflow.com/questions/30370220/how-to-diable-break-line-keystroke-enter-e-g-in-jtextarea-in-swing-java
 * Answer by Normal design (https://stackoverflow.com/users/4919947/normal-design)
 */

package Fleissner;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Gui {

  private JTextArea outputArea;

  public Gui() {
    //JFrame
    JFrame frame = new JFrame("Fleissner-Encryption");
    frame.setSize(500, 300);
    frame.setMinimumSize(new Dimension(500, 300));
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //JPanel
    JPanel panel = new JPanel(new GridBagLayout());

    //Creating textArea for input
    JTextArea inputArea = new JTextArea();
    inputArea.setColumns(50);
    inputArea.setRows(4);
    inputArea.setLineWrap(true);
    inputArea.getDocument().putProperty("filterNewlines", Boolean.TRUE);    //Make line-breaks impossible
    inputArea.setFont(new Font("monospaced", Font.PLAIN, 12));
    JScrollPane input = new JScrollPane(inputArea);

    //Creating button
    JButton button = new JButton("Encrypt");
    button.setFont(new Font("Arial", Font.PLAIN, 20));
    button.addActionListener(e -> encrypt(inputArea.getText()));

    //Creating textArea for output
    outputArea = new JTextArea();
    outputArea.setEditable(false);
    outputArea.setColumns(50);
    outputArea.setRows(10);
    outputArea.setLineWrap(true);
    outputArea.setFont(new Font("monospaced", Font.PLAIN, 12));
    JScrollPane output = new JScrollPane(outputArea);

    //Layout
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.weighty = 5;
    gbc.gridwidth = GridBagConstraints.REMAINDER;

    //Add everything to panel
    panel.add(input, gbc);
    panel.add(button, gbc);
    panel.add(output, gbc);

    //Add everything to frame
    frame.add(panel);
    frame.setVisible(true);
  }

  //Connection to class Fleissner
  private void encrypt(String text) {
    Fleissner fleissner = new Fleissner(text);
    String result = fleissner.fleissnerEncryption();
    outputArea.setText(result);
  }
}

Question

  • How can the code be improved in general?
  • What improvements are possible in regard to the GUI?
  • How can the codestructure be improved?

[1] Wikipedia contributors. (2019, March 13, 05:57 UTC), Fleißnersche Schablone. In Wikipedia, Die freie Enzykopädie. Retrieved June 4, 2020, 17:20 UTC from https://de.wikipedia.org/w/index.php?title=Flei%C3%9Fnersche_Schablone&oldid=186533989


\$\endgroup\$
2
\$\begingroup\$

In my opinion, the code is great and well separated (ui vs logic), good job!

I have some suggestions.

Use java.lang.StringBuilder to concatenate String in a loop.

It's generally more efficient to use the builder in a loop, since the compiler is unable to make it efficient in a loop; since it creates a new String each iteration. There are lots of good explanations with more details on the subject.

Use the enhanced for loop when possible

In this case, it will make the code shorter.

Fleissner#fleissnerEncryption

Before

for (int i = 0; i < out.length; i++) {
   //[...]
   result += out[i][j] + " ";
}

After

for (char[] chars : out) {
   //[...]
   result += chars[j] + " ";
}

You can simplify the array in Fleissner#encrypt since "\$0 * n = 0\$" and "\$1 * n = n\$"

Before

int[] ar = {0 * substringSize, 1 * substringSize, 2 * substringSize, 3 * substringSize, 4 * substringSize};

After

int[] ar = {0, substringSize, 2 * substringSize, 3 * substringSize, 4 * substringSize};
\$\endgroup\$
2
  • \$\begingroup\$ Thanks for your feedback. What do you think about the constructor of the Fleissner-Class? Should I put the code needed to determine the grille-size in a seperate method, or is it ok to do this calculation in the constructor? \$\endgroup\$ – Philipp Wilhelm Jun 5 '20 at 14:43
  • 1
    \$\begingroup\$ @vulpini99 In my opinion, there are no issues to do some calculation in the constructor, since you would need to pass the data to it if not. Another alternative that I see is you could set the constructor private and create a static builder method that instantiate the instance and pass the calculated values to the constructor. This method will allow you the group the logic of the calculations and remove them from the constructor and be easy to use. \$\endgroup\$ – Doi9t Jun 5 '20 at 15:01
1
+50
\$\begingroup\$

The code is well structured and division between math core and gui is neat, some my thoughts about math part. Your Fleissner constructor is the following:

public Fleissner(String text) {
    this.text = text;
    // determine needed grille-size
    int length = text.length();
    if (length == 0) {
      length++;
    }
    while (true) {
      // grilles with size > 64 cannot be displayed
      if (length > 64) {
        grilleSize = 64;
        substringSize = grilleSize / 4;
        sizeSqrt = (int) Math.sqrt((double) grilleSize);
        break;
      }
      // conditions for grille-size-numbers.
      // Possible are - for example - 2x2, 4x4, 6x6 and 8x8
      if (Math.sqrt((double) length) % 2 == 0 && length % 4 == 0) {
        grilleSize = length;
        substringSize = grilleSize / 4;
        sizeSqrt = (int) Math.sqrt((double) grilleSize);
        break;
      }
      length++;
    }
  }
}

You can construct a maximal grille of 8 X 8 characters, with possible options of 2 X 2, 4 X 4, 6 X 6 and 8 X 8 grilles according to the length of the string you are using; you can achieve the same result in a less complicate the way like this:

public Fleissner(String text) {
    this.text = text;
    final int l = text.length();
    int length = 2;
    for (;l > length * length && length < 8; length += 2);
    this.grilleSize = length * length;
    this.substringSize = grilleSize / 4;
    this.length = length;       
}

In your method fleissnerEncryption you have the following lines:

if (text.length() < grilleSize) {
     while (text.length() < grilleSize) {
        text = text + (char) ('a' + (int) (Math.random() * (('z' - 'a') + 1)));
     }
}

You can use Random.ints method and rewrite like this:

int l = text.length();
if (l < grilleSize) {
    new Random().ints(grilleSize - l, 'a', 'z' + 1)
                .forEach(i -> { text = text + (char)i; });
}

The most complicate part is about writing the String result; you have the following lines:

String result = "Encrypted:        ";
int length = result.length();
result += "|  Grille:\n";
for (int i = 0; i < out.length; i++) {
  for (int j = 0; j < out[0].length; j++) {
    if (j == sizeSqrt) {
      for (int k = 0; k < length - 2 * sizeSqrt; k++) {
        result += " ";
      }
      result += "|  ";
    }
    result += out[i][j] + " ";
  }
  result += "\n";
}

Using String.join method and Collections.nCopies you can rewrite it in the following way:

String result = "Encrypted:        ";
l = result.length();
String spaces = String.join("", Collections.nCopies(l - 2 * length, " "));
result += "|  Grille:\n";
for (char[] row : out) {
    result += String.join(" ", new String(Arrays.copyOfRange(row, 0, length)).split(""));
    result += spaces;
    result += " |  ";
    result += String.join(" ", new String(Arrays.copyOfRange(row, length, row.length)).split(""));
    result += "\n";
}

return result;

The method fleissnerEncryption can be rewritten like this:

public String fleissnerEncryption() {

    // If text is too short for grille, the grille will be filled up with random chars ('a' - 'z')
    int l = text.length();
    if (l < grilleSize) {
        new Random().ints(grilleSize - l, 'a', 'z' + 1)
                    .forEach(i -> { text = text + (char)i; });
    }

    if (l != grilleSize) {
        text = safeSubstring(text, 0, grilleSize);
    }

    out = encrypt();

    //Convert array "out" to String in desired format
    String result = "Encrypted:        ";
    l = result.length();
    String spaces = String.join("", Collections.nCopies(l - 2 * length, " "));
    result += "|  Grille:\n";
    for (char[] row : out) {
        result += String.join(" ", new String(Arrays.copyOfRange(row, 0, length)).split(""));
        result += spaces;
        result += " |  ";
        result += String.join(" ", new String(Arrays.copyOfRange(row, length, row.length)).split(""));
        result += "\n";
    }

    return result;
}

The following line in your code :

int[] ar = {0 * substringSize, 1 * substringSize, 2 * substringSize, 3 * substringSize, 4 * substringSize};

It can be rewritten with IntStream :

int[] ar = IntStream.rangeClosed(0, 5).map(i -> i * substringSize).toArray();

The name of your package Fleissner should be fleissner. By convention, package names usually start with a lowercase letter

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.