Initial commit
[debian/openrocket] / src / net / sf / openrocket / util / Base64.java
1 package net.sf.openrocket.util;
2
3 import java.util.Arrays;
4 import java.util.HashMap;
5 import java.util.Map;
6 import java.util.Random;
7
8 public class Base64 {
9
10         public static final int DEFAULT_CHARS_PER_LINE = 72;
11         
12         private static final char[] ALPHABET = new char[] {
13                         'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
14                         'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
15                         'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
16                         'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
17         };
18         private static final char PAD = '=';
19         
20         private static final Map<Character,Integer> REVERSE = new HashMap<Character,Integer>();
21         static {
22                 for (int i=0; i<64; i++) {
23                         REVERSE.put(ALPHABET[i], i);
24                 }
25                 REVERSE.put('-', 62);
26                 REVERSE.put('_', 63);
27                 REVERSE.put(PAD, 0);
28         }
29         
30         
31         public static String encode(byte[] data) {
32                 return encode(data, DEFAULT_CHARS_PER_LINE);
33         }
34         
35         public static String encode(byte[] data, int maxColumn) {
36                 StringBuilder builder = new StringBuilder();
37                 int column = 0;
38                 
39                 for (int position=0; position < data.length; position+=3) {
40                         if (column+4 > maxColumn) {
41                                 builder.append('\n');
42                                 column = 0;
43                         }
44                         builder.append(encodeGroup(data, position));
45                         column += 4;
46                 }
47                 builder.append('\n');
48                 return builder.toString();
49         }
50         
51         
52
53         
54         public static byte[] decode(String data) {
55                 byte[] array = new byte[data.length()*3/4];
56                 char[] block = new char[4];
57                 int length = 0;
58                 
59                 for (int position=0; position < data.length(); ) {
60                         int p;
61                         for (p=0; p<4 && position < data.length(); position++) {
62                                 char c = data.charAt(position);
63                                 if (!Character.isWhitespace(c)) {
64                                         block[p] = c;
65                                         p++;
66                                 }
67                         }
68                         
69                         if (p==0)
70                                 break;
71                         if (p!=4) {
72                                 throw new IllegalArgumentException("Data ended when decoding Base64, p="+p);
73                         }
74                         
75                         int l = decodeGroup(block, array, length);
76                         length += l;
77                         if (l < 3)
78                                 break;
79                 }
80                 return Arrays.copyOf(array, length);
81         }
82         
83         
84         ////  Helper methods
85         
86         
87         /**
88          * Encode three bytes of data into four characters.
89          */
90         private static char[] encodeGroup(byte[] data, int position) {
91                 char[] c = new char[] { '=','=','=','=' };
92                 int b1=0, b2=0, b3=0;
93                 int length = data.length - position;
94                 
95                 if (length == 0)
96                         return c;
97                 
98                 if (length >= 1) {
99                         b1 = ((int)data[position])&0xFF;
100                 }
101                 if (length >= 2) {
102                         b2 = ((int)data[position+1])&0xFF;
103                 }
104                 if (length >= 3) {
105                         b3 = ((int)data[position+2])&0xFF;
106                 }
107                 
108                 c[0] = ALPHABET[b1>>2];
109                 c[1] = ALPHABET[(b1 & 3)<<4 | (b2>>4)];
110                 if (length == 1)
111                         return c;
112                 c[2] = ALPHABET[(b2 & 15)<<2 | (b3>>6)];
113                 if (length == 2)
114                         return c;
115                 c[3] = ALPHABET[b3 & 0x3f];
116                 return c;
117         }
118         
119         
120         /**
121          * Decode four chars from data into 0-3 bytes of data starting at position in array.
122          * @return      the number of bytes decoded.
123          */
124         private static int decodeGroup(char[] data, byte[] array, int position) {
125                 int b1, b2, b3, b4;
126                 
127                 try {
128                         b1 = REVERSE.get(data[0]);
129                         b2 = REVERSE.get(data[1]);
130                         b3 = REVERSE.get(data[2]);
131                         b4 = REVERSE.get(data[3]);
132                 } catch (NullPointerException e) {
133                         // If auto-boxing fails
134                         throw new IllegalArgumentException("Illegal characters in the sequence to be "+
135                                         "decoded: "+Arrays.toString(data));
136                 }
137                 
138                 array[position]   = (byte)((b1 << 2) | (b2 >> 4)); 
139                 array[position+1] = (byte)((b2 << 4) | (b3 >> 2)); 
140                 array[position+2] = (byte)((b3 << 6) | (b4)); 
141                 
142                 // Check the amount of data decoded
143                 if (data[0] == PAD)
144                         return 0;
145                 if (data[1] == PAD) {
146                         throw new IllegalArgumentException("Illegal character padding in sequence to be "+
147                                         "decoded: "+Arrays.toString(data));
148                 }
149                 if (data[2] == PAD)
150                         return 1;
151                 if (data[3] == PAD)
152                         return 2;
153                 
154                 return 3;
155         }
156         
157         
158         
159         public static void main(String[] arg) {
160                 Random rnd = new Random();
161                 
162                 for (int round=0; round < 1000; round++) {
163                         int n = rnd.nextInt(1000);
164                         n = 100000;
165                         
166                         byte[] array = new byte[n];
167                         rnd.nextBytes(array);
168
169                         String encoded = encode(array);
170                         
171                         System.out.println(encoded);
172                         System.exit(0);
173 //                      for (int i=0; i<1000; i++) {
174 //                              int pos = rnd.nextInt(encoded.length());
175 //                              String s1 = encoded.substring(0, pos);
176 //                              String s2 = encoded.substring(pos);
177 //                              switch (rnd.nextInt(15)) {
178 //                              case 0:
179 //                                      encoded = s1 + " " + s2;
180 //                                      break;
181 //                              case 1:
182 //                                      encoded = s1 + "\u0009" + s2;
183 //                                      break;
184 //                              case 2:
185 //                                      encoded = s1 + "\n" + s2;
186 //                                      break;
187 //                              case 3:
188 //                                      encoded = s1 + "\u000B" + s2;
189 //                                      break;
190 //                              case 4:
191 //                                      encoded = s1 + "\r" + s2;
192 //                                      break;
193 //                              case 5:
194 //                                      encoded = s1 + "\u000C" + s2;
195 //                                      break;
196 //                              case 6:
197 //                                      encoded = s1 + "\u001C" + s2;
198 //                                      break;
199 //                              }
200 //                      }
201                         
202                         byte[] decoded = null;
203                         try {
204                                 decoded = decode(encoded);
205                         } catch (IllegalArgumentException e) {
206                                 e.printStackTrace();
207                                 System.err.println("Bad data:\n"+encoded);
208                                 System.exit(1);
209                         }
210                         
211                         if (!Arrays.equals(array, decoded)) {
212                                 System.err.println("Data differs!  n="+n);
213                                 System.exit(1);
214                         }
215                         System.out.println("n="+n+" ok!");
216                 }
217         }
218         
219         
220 }