Revision 561

tmp/org.txm.ca.core/src/org/txm/ca/core/messages/CACoreMessages.java (revision 561)
3 3
import org.eclipse.osgi.util.NLS;
4 4
import org.txm.utils.messages.Utf8NLS;
5 5

  
6
public class CACoreMessages extends NLS {
7

  
6
public class CACoreMessages extends Utf8NLS {
7
	static {
8
		Utf8NLS.initializeMessages(CACoreMessages.class);
9
	}
8 10
	private static final String BUNDLE_NAME = "org.txm.ca.core.messages.messages"; //$NON-NLS-1$
9 11

  
10 12
	public static String charts_error_factorialMap_cantCreateCAChartFile;
......
49 51
	public static String error_lexicalTableNotFoundInWorkspace;
50 52
	public static String error_unableToExtractSingularValues;
51 53

  
52
	
53
	static {
54
		// initialize resource bundle
55
		Utf8NLS.initializeMessages(BUNDLE_NAME, CACoreMessages.class);
56
	}
57

  
58
	private CACoreMessages() {
59
	}
60 54
}
tmp/org.txm.cah.rcp/src/org/txm/cah/rcp/messages/AHCUIMessages.java (revision 561)
3 3
import org.eclipse.osgi.util.NLS;
4 4
import org.txm.utils.messages.Utf8NLS;
5 5

  
6
public class AHCUIMessages extends NLS {
7

  
8
	private static final String BUNDLE_NAME = "org.txm.cah.rcp.messages.messages"; //$NON-NLS-1$
6
public class AHCUIMessages extends Utf8NLS {
9 7
	
8
	static {
9
		Utf8NLS.initializeMessages(AHCUIMessages.class);
10
	}
10 11
	
11 12
	public static String CAHPreferencePage_0;
12 13
	public static String CAHPreferencePage_2;
......
27 28
	public static String COMPUTE_ROWS;
28 29
	
29 30
	public static String CHART_TOOLBAR_BUTTON_NUMBER_OF_CLUSTERS;
30
	
31
	static {
32
		// initializes resource bundle
33
		Utf8NLS.initializeMessages(BUNDLE_NAME, AHCUIMessages.class);
34
	}
35
	
36
	private AHCUIMessages() {
37
		//no instantiation
38
	}
39 31
}
tmp/org.txm.rcp/src/main/java/org/txm/rcp/editors/TXMEditorToolBar.java (revision 561)
41 41
	 * @param parent
42 42
	 * @param style
43 43
	 * @param toolbarLocalId
44
	 * @param resultEditorPart
44
	 * @param editor
45 45
	 */
46
	public TXMEditorToolBar(Composite parent, int style, String toolbarLocalId, TXMEditorPart resultEditorPart) {
47
		super(parent, style);
46
	public TXMEditorToolBar(TXMEditorPart editor, int style, String toolbarLocalId) {
47
		//TODO change the test with ;
48
		super(editor.getToolBarContainer(toolbarLocalId), style);
48 49

  
49
		this.editorPart = resultEditorPart;
50
		this.editorPart = editor;
50 51

  
51 52
		// permit to contribute via plugin.xml menu extension
52 53
		ToolBarManager manager = new ToolBarManager(this);
tmp/org.txm.rcp/src/main/java/org/txm/rcp/editors/TXMEditorPart.java (revision 561)
79 79
	 * State value to create or not the computing parameters area and tool bar button.
80 80
	 */
81 81
	protected boolean createComputingParametersArea = true;
82
	/**
83
	 * contains the bottom toolbars
84
	 */
85
	protected Composite bottomToolBarContainer;
82 86
	
83 87
	
84 88
	@Override
......
107 111
			// create the top tool bar
108 112
			this.topToolBarContainer = new Composite(parent, SWT.NONE);
109 113
			this.topToolBarContainer.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
110
			
111 114
			RowLayout rl = new RowLayout(SWT.HORIZONTAL);
112 115
			rl.marginTop = rl.marginHeight = rl.marginWidth = rl.marginBottom = 0;
113 116
			this.topToolBarContainer.setLayout(rl);
114 117
			
115
			this.topToolBar = new TXMEditorToolBar(this.topToolBarContainer, SWT.FLAT | SWT.RIGHT, "top", this);
118
			this.topToolBar = new TXMEditorToolBar(this, SWT.FLAT | SWT.RIGHT, "top");
116 119
			
117 120
			this.parametersGroupsComposite = new Composite(parent, SWT.NONE);
118 121
			GridLayout gl = new GridLayout(1, true);
......
136 139
			this.displayArea = new Composite(parent, SWT.NONE);
137 140
			this.displayArea.setLayoutData(new GridData(GridData.FILL_BOTH));
138 141
			
142
			// create the top tool bar
143
			this.bottomToolBarContainer = new Composite(parent, SWT.NONE);
144
			this.bottomToolBarContainer.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
145
			rl = new RowLayout(SWT.HORIZONTAL);
146
			rl.marginTop = rl.marginHeight = rl.marginWidth = rl.marginBottom = 0;
147
			this.bottomToolBarContainer.setLayout(rl);
148
			
139 149
			// bottom tool bar
140
			this.bottomToolBar = new TXMEditorToolBar(parent, SWT.FLAT, "bottom", this);
150
			this.bottomToolBar = new TXMEditorToolBar(this, SWT.FLAT, "bottom");
141 151
		}
142 152
		catch(Exception e) {
143 153
			System.err.println("TXMEditorPart.createPartControl(): can not create the editor.");
......
149 159
		return topToolBarContainer;
150 160
	}
151 161
	
162
	public Composite getBottomToolBarContainer() {
163
		return bottomToolBarContainer;
164
	}
165
	
152 166
	public TXMEditorToolBar getTopToolbar() {
153 167
		return topToolBar;
154 168
	}
......
649 663
		}
650 664
		viewer.getControl().setRedraw(true);
651 665
	}
666

  
667
	public Composite getToolBarContainer(String toolbarLocalId) {
668
		if ("top".equals(toolbarLocalId)) {
669
			return this.getTopToolBarContainer();
670
		} else if ("bottom".equals(toolbarLocalId)) {
671
			return this.getBottomToolBarContainer();
672
		}
673
		return this.getTopToolBarContainer(); // default toolbar container
674
	}
652 675
}
tmp/org.txm.rcp/src/main/java/org/txm/rcp/swt/dialog/QueryAssistDialog.java (revision 561)
53 53
import org.txm.searchengine.cqp.corpus.Corpus;
54 54
import org.txm.searchengine.cqp.corpus.Property;
55 55
import org.txm.searchengine.cqp.corpus.StructuralUnit;
56
import org.txm.searchengine.cqp.corpus.WordProperty;
56 57
import org.txm.rcp.IImageKeys;
57 58
import org.txm.rcp.RCPMessages;
58 59
// TODO: Auto-generated Javadoc
......
86 87
	List<LinkField> linkfields = new ArrayList<LinkField>();
87 88

  
88 89
	/** The properties. */
89
	List<Property> properties;
90
	List<WordProperty> properties;
90 91

  
91 92
	/** The cherche label. */
92 93
	Label chercheLabel;
tmp/org.txm.backtomedia.rcp/src/vlcplayerrcp/MessagesMP.java (revision 561)
1 1
package vlcplayerrcp;
2 2

  
3
import org.eclipse.osgi.util.NLS;
3
import org.txm.utils.messages.Utf8NLS;
4 4

  
5
public class MessagesMP extends NLS {
5
public class MessagesMP extends Utf8NLS {
6
	static {
7
		Utf8NLS.initializeMessages(MessagesMP.class);
8
	}
9
	
6 10
	private static final String BUNDLE_NAME = "vlcplayerrcp.messages"; //$NON-NLS-1$
7 11
	public static String BackToMedia_0;
8 12
	public static String BackToMedia_1;
......
44 48
	public static String stop;
45 49
	public static String time_range;
46 50
	public static String volume;
47
	static {
48
		// initialize resource bundle
49
		NLS.initializeMessages(BUNDLE_NAME, MessagesMP.class);
50
	}
51

  
52
	private MessagesMP() {
53
	}
54 51
}
tmp/org.txm.backtomedia.rcp/src/org/txm/backtomedia/editors/vlcplayer/VLCPlayerEditor.java (revision 561)
6 6
import org.eclipse.ui.IEditorInput;
7 7
import org.eclipse.ui.IEditorSite;
8 8
import org.eclipse.ui.PartInitException;
9
import org.eclipse.ui.part.EditorPart;
9 10
import org.txm.core.results.TXMResult;
10 11
import org.txm.rcp.editors.TXMEditorPart;
11 12

  
12
public class VLCPlayerEditor extends TXMEditorPart {
13
public class VLCPlayerEditor extends EditorPart {
13 14
	
14 15
	/** The Constant ID. */
15 16
	public static final String ID = VLCPlayerEditor.class.getCanonicalName(); //$NON-NLS-1$
......
72 73
	public VLCPlayer getPlayer() {
73 74
		return player;
74 75
	}
76
	
77
	public void setPartName(String partName) {
78
		super.setPartName(partName);
79
	}
75 80

  
76

  
77 81
	@Override
78
	public boolean isResultUsingParent(TXMResult parent) {
82
	public boolean isDirty() {
79 83
		return false;
80 84
	}
81

  
82
	@Override
83
	public void updateResultFromEditor() {
84
		// nothing to do
85
	}
86 85
}
tmp/org.txm.wordcloud.core/src/org/txm/wordcloud/core/functions/WordCloud.java (revision 561)
1 1
package org.txm.wordcloud.core.functions;
2 2

  
3 3
import java.io.File;
4
import java.util.ArrayList;
5 4
import java.util.List;
6 5

  
7 6
import org.txm.chartsengine.core.results.ChartResult;
......
9 8
import org.txm.core.results.TXMParameters;
10 9
import org.txm.index.core.functions.Index;
11 10
import org.txm.index.core.functions.Line;
12
import org.txm.searchengine.cqp.corpus.Corpus;
13
import org.txm.searchengine.cqp.corpus.Property;
14
import org.txm.searchengine.cqp.corpus.query.Query;
15 11
import org.txm.statsengine.r.core.RWorkspace;
16 12
import org.txm.utils.logger.Log;
17 13
import org.txm.wordcloud.core.messages.WordCloudCoreMessages;
18 14
import org.txm.wordcloud.core.preferences.WordCloudPreferences;
19 15

  
20 16
/**
21
 * Word cloud.
17
 * WordCloud computes word clouds using the "wordcloud" R package and an Index result
22 18
 * 
23 19
 * @author mdecorde
24 20
 * @author sjacquot
......
29 25
	//TODO: test rw.setStringEncoding("UTF-8"); // or connection
30 26
	private int[] freqs;
31 27
	private String[] labels;
32
	private Object source;
28
	
29
	protected Integer pFMinFilter;
30
	
31
	protected Integer pVMaxFilter;
33 32
		
34
//	public WordCloud(LexicalTable table) throws RWorkspaceException, REXPMismatchException {
35
//		parent = table.getPartition();
36
//		if (parent == null) parent = table.getCorpus();
37
//		
38
//		RWorkspace rw = RWorkspace.getRWorkspaceInstance();
39
//		this.labels = rw.evalToString("rownames("+table.getSymbol()+")");
40
//		this.freqs = rw.eval("rowSums("+table.getSymbol()+")").asIntegers();
41
//		
42
//		removeDiacritics();
43
//	}
44

  
45 33
	/**
46 34
	 * 
47 35
	 * @param index
48 36
	 */
49 37
	public WordCloud(Index index) {
50 38
		super(index);
51
		this.source = index;
52
		//removeDiacritics();
53 39
	}
54 40
		
55 41
	/**
......
57 43
	 * @param index
58 44
	 */
59 45
	private void initWith(Index index) {
60
		source = index;
61 46
		List<Line> lines = index.getAllLines();
62 47
		this.freqs = new int[lines.size()];
63 48
		this.labels = new String[lines.size()];
......
69 54
			i++;
70 55
		}
71 56
	}
72
	
73 57

  
74
//	public WordCloud(QueryIndex qindex) {
75
//		parent = qindex.getCorpus();
76
//		source = qindex;
77
//		Collection<QueryIndexLine> lines = qindex.getLines();
78
//		this.freqs = new int[lines.size()];
79
//		this.labels = new String[lines.size()];
80
//		
81
//		int i = 0;
82
//		for (QueryIndexLine line : lines) {
83
//			freqs[i] = line.getFrequency();
84
//			labels[i] = line.getName();
85
//		}
86
//		//removeDiacritics();
87
//	}
88
	
89
	
90
	
91 58
	private void removeDiacritics() {
92 59
		//for (int i = 0; i < labels.length ; i++)
93 60
		//	labels[i] = AsciiUtils.convertNonAscii(labels[i]);
......
186 153
	
187 154
	@Override
188 155
	public String getSimpleName() {
189
		return WordCloudCoreMessages.RESULT_TYPE + " (" + this.getIntParameterValue(WordCloudPreferences.FMIN)
156
		return WordCloudCoreMessages.RESULT_TYPE + " (" + this.pFMinFilter
190 157
				+ " / "
191
				+ this.getIntParameterValue(WordCloudPreferences.MAX_WORDS)
158
				+ this.pVMaxFilter
192 159
				+ ")";
193 160
	}
194 161

  
......
200 167
	
201 168
	@Override
202 169
	public boolean canCompute() {
203
		return source != null;
170
		return parent != null && pFMinFilter != null;
204 171
	}
205 172

  
206 173
	@Override
207
	public boolean saveParameters() {
208
		// TODO Auto-generated method stub
209
		System.err.println("WordCloud.saveParameters(): not yet implemented.");
210
		return true;
211
	}
212

  
213
	@Override
214 174
	public boolean setParameters(TXMParameters parameters) {
215
		// TODO Auto-generated method stub
216
		System.err.println("WordCloud.setParameters(): not yet implemented.");
175
		
176
		if (parameters.containsKey(WordCloudPreferences.FMIN)) {
177
			Object value = parameters.get(WordCloudPreferences.FMIN);
178
			if (value instanceof String) {
179
				this.pFMinFilter = Integer.parseInt(value.toString());
180
			} else if (value instanceof Integer) {
181
				this.pFMinFilter = (Integer)value;
182
			}
183
		}
184
		
185
		if (parameters.containsKey(WordCloudPreferences.MAX_WORDS)) {
186
			Object value = parameters.get(WordCloudPreferences.MAX_WORDS);
187
			if (value instanceof String) {
188
				this.pVMaxFilter = Integer.parseInt(value.toString());
189
			} else if (value instanceof Integer) {
190
				this.pVMaxFilter = (Integer)value;
191
			}
192
		}
193
		
217 194
		return true;
218 195
	}
219 196

  
......
237 214
	
238 215
	@Override
239 216
	protected boolean _compute(boolean update) {
240
		// FIXME: source must always be an Index because the corpus source produces an Index
241
		// from corpus
242
		if (source instanceof Corpus)	{ 
243
			try {
244
				Corpus corpus = (Corpus) source;
245
				Query query;
246
				String lang = corpus.getLang();
247
				if (corpus.getProperty(lang + "pos") != null) {
248
					query = new Query(getQueryForLang(lang));
249
				}
250
				else {
251
					query = new Query("[]");
252
				}
253
				
254
				System.out.println("Used query: " + query);
255
				
256
				ArrayList<Property> props = new ArrayList<Property>();
257
				props.add(corpus.getWordProperty());
258
				Index index = new Index(corpus);
259
				index.setParameters(query, props, null, null, null, null);
260
				if (!query.toString().equals("[]")) {
261
					corpus.addChild(index);
262
				}
263

  
264
				index.compute(false, monitor);
265
				
266
				index.addChild(this);
267
					
268
				List<Line> lines = index.getAllLines();
269
				if (lines != null && lines.size() > 0) {
270
					this.initWith(index);
271
				}
272
				else {
273
					Log.severe("Error: Could not build a word selection with this corpus");
274
					throw new IllegalArgumentException("Error: Index failed with corpus " + corpus + " with lang " + lang + " and query " + query);
275
				}
276
			}
277
			catch(Exception e) {
278
				System.out.println("Error: "+e.getLocalizedMessage());
279
				Log.printStackTrace(e);
280
				return false;
281
			}
282
		}
283
		// from index
284
		else if(this.source instanceof Index)	{
285
			this.initWith((Index) this.source);
286
			return true;
287
		}
288
		
217
//		// FIXME: source must always be an Index because the corpus source produces an Index
218
//		// from corpus
219
//		if (source instanceof Corpus)	{ 
220
//			try {
221
//				Corpus corpus = (Corpus) source;
222
//				Query query;
223
//				String lang = corpus.getLang();
224
//				if (corpus.getProperty(lang + "pos") != null) {
225
//					query = new Query(getQueryForLang(lang));
226
//				}
227
//				else {
228
//					query = new Query("[]");
229
//				}
230
//				
231
//				System.out.println("Used query: " + query);
232
//				
233
//				ArrayList<Property> props = new ArrayList<Property>();
234
//				props.add(corpus.getWordProperty());
235
//				Index index = new Index(corpus);
236
//				index.setParameters(query, props, null, null, null, null);
237
//				if (!query.toString().equals("[]")) {
238
//					corpus.addChild(index);
239
//				}
240
//
241
//				index.compute(false, monitor);
242
//				
243
//				index.addChild(this);
244
//					
245
//				List<Line> lines = index.getAllLines();
246
//				if (lines != null && lines.size() > 0) {
247
//					this.initWith(index);
248
//				}
249
//				else {
250
//					Log.severe("Error: Could not build a word selection with this corpus");
251
//					throw new IllegalArgumentException("Error: Index failed with corpus " + corpus + " with lang " + lang + " and query " + query);
252
//				}
253
//			}
254
//			catch(Exception e) {
255
//				System.out.println("Error: "+e.getLocalizedMessage());
256
//				Log.printStackTrace(e);
257
//				return false;
258
//			}
259
			this.initWith((Index)parent);
260
//		}
261
//		// from index
262
//		else if(this.source instanceof Index)	{
263
//			this.initWith((Index) this.source);
264
//			return true;
265
//		}
266
//		
289 267
		return true;
290 268
	}
291 269

  
270
	@Override
271
	public boolean loadParameters() throws Exception {
272
		this.pFMinFilter = this.getIntParameterValue(WordCloudPreferences.FMIN);
273
		this.pVMaxFilter = this.getIntParameterValue(WordCloudPreferences.MAX_WORDS);
274
		return false;
275
	}
292 276

  
277
	public Integer getFMinFilter() {
278
		return pFMinFilter;
279
	}
280

  
281
	public Integer getVMaxFilter() {
282
		return pVMaxFilter;
283
	}
293 284
}
tmp/org.txm.ca.rcp/src/org/txm/ca/rcp/messages/CAUIMessages.java (revision 561)
3 3
import org.eclipse.osgi.util.NLS;
4 4
import org.txm.utils.messages.Utf8NLS;
5 5

  
6
public class CAUIMessages extends NLS {
7

  
8
	private static final String BUNDLE_NAME = "org.txm.ca.rcp.messages.messages"; //$NON-NLS-1$
9

  
6
public class CAUIMessages extends Utf8NLS {
7
	
8
	static {
9
		Utf8NLS.initializeMessages(CAUIMessages.class);
10
	}
11
	
10 12
	public static String PreferencePage_0;
11 13
	public static String PreferencePage_1;
12 14
	public static String PreferencePage_2;
......
48 50
	public static String CorrespondanceAnalysisEditorInput_9;
49 51
	
50 52
	public static String PreferencePage_CHART_SHOW_POINT_SHAPES;
51
	
52
	
53
	static {
54
		// initializes resource bundle
55
		Utf8NLS.initializeMessages(BUNDLE_NAME, CAUIMessages.class);
56
	}
57

  
58
	private CAUIMessages() {
59
		//no instantiation
60
	}
61 53
}
tmp/org.txm.core/src/java/org/txm/core/preferences/Base64.java (revision 561)
1
/*
2
 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
 *
5
 * This code is free software; you can redistribute it and/or modify it
6
 * under the terms of the GNU General Public License version 2 only, as
7
 * published by the Free Software Foundation.  Oracle designates this
8
 * particular file as subject to the "Classpath" exception as provided
9
 * by Oracle in the LICENSE file that accompanied this code.
10
 *
11
 * This code is distributed in the hope that it will be useful, but WITHOUT
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14
 * version 2 for more details (a copy is included in the LICENSE file that
15
 * accompanied this code).
16
 *
17
 * You should have received a copy of the GNU General Public License version
18
 * 2 along with this work; if not, write to the Free Software Foundation,
19
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
 *
21
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
 * or visit www.oracle.com if you need additional information or have any
23
 * questions.
24
 */
25

  
26
package org.txm.core.preferences;
27

  
28
import java.io.FilterOutputStream;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.OutputStream;
32
import java.nio.ByteBuffer;
33
import java.nio.charset.StandardCharsets;
34
import java.util.Arrays;
35
import java.util.Objects;
36

  
37
/**
38
 * This class consists exclusively of static methods for obtaining
39
 * encoders and decoders for the Base64 encoding scheme. The
40
 * implementation of this class supports the following types of Base64
41
 * as specified in
42
 * <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and
43
 * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
44
 *
45
 * <ul>
46
 * <li><a name="basic"><b>Basic</b></a>
47
 * <p> Uses "The Base64 Alphabet" as specified in Table 1 of
48
 *     RFC 4648 and RFC 2045 for encoding and decoding operation.
49
 *     The encoder does not add any line feed (line separator)
50
 *     character. The decoder rejects data that contains characters
51
 *     outside the base64 alphabet.</p></li>
52
 *
53
 * <li><a name="url"><b>URL and Filename safe</b></a>
54
 * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified
55
 *     in Table 2 of RFC 4648 for encoding and decoding. The
56
 *     encoder does not add any line feed (line separator) character.
57
 *     The decoder rejects data that contains characters outside the
58
 *     base64 alphabet.</p></li>
59
 *
60
 * <li><a name="mime"><b>MIME</b></a>
61
 * <p> Uses the "The Base64 Alphabet" as specified in Table 1 of
62
 *     RFC 2045 for encoding and decoding operation. The encoded output
63
 *     must be represented in lines of no more than 76 characters each
64
 *     and uses a carriage return {@code '\r'} followed immediately by
65
 *     a linefeed {@code '\n'} as the line separator. No line separator
66
 *     is added to the end of the encoded output. All line separators
67
 *     or other characters not found in the base64 alphabet table are
68
 *     ignored in decoding operation.</p></li>
69
 * </ul>
70
 *
71
 * <p> Unless otherwise noted, passing a {@code null} argument to a
72
 * method of this class will cause a {@link java.lang.NullPointerException
73
 * NullPointerException} to be thrown.
74
 *
75
 * @author  Xueming Shen
76
 * @since   1.8
77
 */
78
//TODO: switch to Java 1.8 and drop this copy of java.util.Base64
79
public class Base64 {
80

  
81
    private Base64() {}
82

  
83
    /**
84
     * Returns a {@link Encoder} that encodes using the
85
     * <a href="#basic">Basic</a> type base64 encoding scheme.
86
     *
87
     * @return  A Base64 encoder.
88
     */
89
    public static Encoder getEncoder() {
90
         return Encoder.RFC4648;
91
    }
92

  
93
    /**
94
     * Returns a {@link Encoder} that encodes using the
95
     * <a href="#url">URL and Filename safe</a> type base64
96
     * encoding scheme.
97
     *
98
     * @return  A Base64 encoder.
99
     */
100
    public static Encoder getUrlEncoder() {
101
         return Encoder.RFC4648_URLSAFE;
102
    }
103

  
104
    /**
105
     * Returns a {@link Encoder} that encodes using the
106
     * <a href="#mime">MIME</a> type base64 encoding scheme.
107
     *
108
     * @return  A Base64 encoder.
109
     */
110
    public static Encoder getMimeEncoder() {
111
        return Encoder.RFC2045;
112
    }
113

  
114
    /**
115
     * Returns a {@link Encoder} that encodes using the
116
     * <a href="#mime">MIME</a> type base64 encoding scheme
117
     * with specified line length and line separators.
118
     *
119
     * @param   lineLength
120
     *          the length of each output line (rounded down to nearest multiple
121
     *          of 4). If {@code lineLength <= 0} the output will not be separated
122
     *          in lines
123
     * @param   lineSeparator
124
     *          the line separator for each output line
125
     *
126
     * @return  A Base64 encoder.
127
     *
128
     * @throws  IllegalArgumentException if {@code lineSeparator} includes any
129
     *          character of "The Base64 Alphabet" as specified in Table 1 of
130
     *          RFC 2045.
131
     */
132
    public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) {
133
         Objects.requireNonNull(lineSeparator);
134
         int[] base64 = Decoder.fromBase64;
135
         for (byte b : lineSeparator) {
136
             if (base64[b & 0xff] != -1)
137
                 throw new IllegalArgumentException(
138
                     "Illegal base64 line separator character 0x" + Integer.toString(b, 16));
139
         }
140
         if (lineLength <= 0) {
141
             return Encoder.RFC4648;
142
         }
143
         return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true);
144
    }
145

  
146
    /**
147
     * Returns a {@link Decoder} that decodes using the
148
     * <a href="#basic">Basic</a> type base64 encoding scheme.
149
     *
150
     * @return  A Base64 decoder.
151
     */
152
    public static Decoder getDecoder() {
153
         return Decoder.RFC4648;
154
    }
155

  
156
    /**
157
     * Returns a {@link Decoder} that decodes using the
158
     * <a href="#url">URL and Filename safe</a> type base64
159
     * encoding scheme.
160
     *
161
     * @return  A Base64 decoder.
162
     */
163
    public static Decoder getUrlDecoder() {
164
         return Decoder.RFC4648_URLSAFE;
165
    }
166

  
167
    /**
168
     * Returns a {@link Decoder} that decodes using the
169
     * <a href="#mime">MIME</a> type base64 decoding scheme.
170
     *
171
     * @return  A Base64 decoder.
172
     */
173
    public static Decoder getMimeDecoder() {
174
         return Decoder.RFC2045;
175
    }
176

  
177
    /**
178
     * This class implements an encoder for encoding byte data using
179
     * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
180
     *
181
     * <p> Instances of {@link Encoder} class are safe for use by
182
     * multiple concurrent threads.
183
     *
184
     * <p> Unless otherwise noted, passing a {@code null} argument to
185
     * a method of this class will cause a
186
     * {@link java.lang.NullPointerException NullPointerException} to
187
     * be thrown.
188
     *
189
     * @see     Decoder
190
     * @since   1.8
191
     */
192
    public static class Encoder {
193

  
194
        private final byte[] newline;
195
        private final int linemax;
196
        private final boolean isURL;
197
        private final boolean doPadding;
198

  
199
        private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
200
            this.isURL = isURL;
201
            this.newline = newline;
202
            this.linemax = linemax;
203
            this.doPadding = doPadding;
204
        }
205

  
206
        /**
207
         * This array is a lookup table that translates 6-bit positive integer
208
         * index values into their "Base64 Alphabet" equivalents as specified
209
         * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
210
         */
211
        private static final char[] toBase64 = {
212
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
213
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
214
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
215
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
216
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
217
        };
218

  
219
        /**
220
         * It's the lookup table for "URL and Filename safe Base64" as specified
221
         * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
222
         * '_'. This table is used when BASE64_URL is specified.
223
         */
224
        private static final char[] toBase64URL = {
225
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
226
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
227
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
228
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
229
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
230
        };
231

  
232
        private static final int MIMELINEMAX = 76;
233
        private static final byte[] CRLF = new byte[] {'\r', '\n'};
234

  
235
        static final Encoder RFC4648 = new Encoder(false, null, -1, true);
236
        static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
237
        static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
238

  
239
        private final int outLength(int srclen) {
240
            int len = 0;
241
            if (doPadding) {
242
                len = 4 * ((srclen + 2) / 3);
243
            } else {
244
                int n = srclen % 3;
245
                len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
246
            }
247
            if (linemax > 0)                                  // line separators
248
                len += (len - 1) / linemax * newline.length;
249
            return len;
250
        }
251

  
252
        /**
253
         * Encodes all bytes from the specified byte array into a newly-allocated
254
         * byte array using the {@link Base64} encoding scheme. The returned byte
255
         * array is of the length of the resulting bytes.
256
         *
257
         * @param   src
258
         *          the byte array to encode
259
         * @return  A newly-allocated byte array containing the resulting
260
         *          encoded bytes.
261
         */
262
        public byte[] encode(byte[] src) {
263
            int len = outLength(src.length);          // dst array size
264
            byte[] dst = new byte[len];
265
            int ret = encode0(src, 0, src.length, dst);
266
            if (ret != dst.length)
267
                 return Arrays.copyOf(dst, ret);
268
            return dst;
269
        }
270

  
271
        /**
272
         * Encodes all bytes from the specified byte array using the
273
         * {@link Base64} encoding scheme, writing the resulting bytes to the
274
         * given output byte array, starting at offset 0.
275
         *
276
         * <p> It is the responsibility of the invoker of this method to make
277
         * sure the output byte array {@code dst} has enough space for encoding
278
         * all bytes from the input byte array. No bytes will be written to the
279
         * output byte array if the output byte array is not big enough.
280
         *
281
         * @param   src
282
         *          the byte array to encode
283
         * @param   dst
284
         *          the output byte array
285
         * @return  The number of bytes written to the output byte array
286
         *
287
         * @throws  IllegalArgumentException if {@code dst} does not have enough
288
         *          space for encoding all input bytes.
289
         */
290
        public int encode(byte[] src, byte[] dst) {
291
            int len = outLength(src.length);         // dst array size
292
            if (dst.length < len)
293
                throw new IllegalArgumentException(
294
                    "Output byte array is too small for encoding all input bytes");
295
            return encode0(src, 0, src.length, dst);
296
        }
297

  
298
        /**
299
         * Encodes the specified byte array into a String using the {@link Base64}
300
         * encoding scheme.
301
         *
302
         * <p> This method first encodes all input bytes into a base64 encoded
303
         * byte array and then constructs a new String by using the encoded byte
304
         * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1
305
         * ISO-8859-1} charset.
306
         *
307
         * <p> In other words, an invocation of this method has exactly the same
308
         * effect as invoking
309
         * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}.
310
         *
311
         * @param   src
312
         *          the byte array to encode
313
         * @return  A String containing the resulting Base64 encoded characters
314
         */
315
        @SuppressWarnings("deprecation")
316
        public String encodeToString(byte[] src) {
317
            byte[] encoded = encode(src);
318
            return new String(encoded, 0, 0, encoded.length);
319
        }
320

  
321
        /**
322
         * Encodes all remaining bytes from the specified byte buffer into
323
         * a newly-allocated ByteBuffer using the {@link Base64} encoding
324
         * scheme.
325
         *
326
         * Upon return, the source buffer's position will be updated to
327
         * its limit; its limit will not have been changed. The returned
328
         * output buffer's position will be zero and its limit will be the
329
         * number of resulting encoded bytes.
330
         *
331
         * @param   buffer
332
         *          the source ByteBuffer to encode
333
         * @return  A newly-allocated byte buffer containing the encoded bytes.
334
         */
335
        public ByteBuffer encode(ByteBuffer buffer) {
336
            int len = outLength(buffer.remaining());
337
            byte[] dst = new byte[len];
338
            int ret = 0;
339
            if (buffer.hasArray()) {
340
                ret = encode0(buffer.array(),
341
                              buffer.arrayOffset() + buffer.position(),
342
                              buffer.arrayOffset() + buffer.limit(),
343
                              dst);
344
                buffer.position(buffer.limit());
345
            } else {
346
                byte[] src = new byte[buffer.remaining()];
347
                buffer.get(src);
348
                ret = encode0(src, 0, src.length, dst);
349
            }
350
            if (ret != dst.length)
351
                 dst = Arrays.copyOf(dst, ret);
352
            return ByteBuffer.wrap(dst);
353
        }
354

  
355
        /**
356
         * Wraps an output stream for encoding byte data using the {@link Base64}
357
         * encoding scheme.
358
         *
359
         * <p> It is recommended to promptly close the returned output stream after
360
         * use, during which it will flush all possible leftover bytes to the underlying
361
         * output stream. Closing the returned output stream will close the underlying
362
         * output stream.
363
         *
364
         * @param   os
365
         *          the output stream.
366
         * @return  the output stream for encoding the byte data into the
367
         *          specified Base64 encoded format
368
         */
369
        public OutputStream wrap(OutputStream os) {
370
            Objects.requireNonNull(os);
371
            return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
372
                                       newline, linemax, doPadding);
373
        }
374

  
375
        /**
376
         * Returns an encoder instance that encodes equivalently to this one,
377
         * but without adding any padding character at the end of the encoded
378
         * byte data.
379
         *
380
         * <p> The encoding scheme of this encoder instance is unaffected by
381
         * this invocation. The returned encoder instance should be used for
382
         * non-padding encoding operation.
383
         *
384
         * @return an equivalent encoder that encodes without adding any
385
         *         padding character at the end
386
         */
387
        public Encoder withoutPadding() {
388
            if (!doPadding)
389
                return this;
390
            return new Encoder(isURL, newline, linemax, false);
391
        }
392

  
393
        private int encode0(byte[] src, int off, int end, byte[] dst) {
394
            char[] base64 = isURL ? toBase64URL : toBase64;
395
            int sp = off;
396
            int slen = (end - off) / 3 * 3;
397
            int sl = off + slen;
398
            if (linemax > 0 && slen  > linemax / 4 * 3)
399
                slen = linemax / 4 * 3;
400
            int dp = 0;
401
            while (sp < sl) {
402
                int sl0 = Math.min(sp + slen, sl);
403
                for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
404
                    int bits = (src[sp0++] & 0xff) << 16 |
405
                               (src[sp0++] & 0xff) <<  8 |
406
                               (src[sp0++] & 0xff);
407
                    dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
408
                    dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
409
                    dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
410
                    dst[dp0++] = (byte)base64[bits & 0x3f];
411
                }
412
                int dlen = (sl0 - sp) / 3 * 4;
413
                dp += dlen;
414
                sp = sl0;
415
                if (dlen == linemax && sp < end) {
416
                    for (byte b : newline){
417
                        dst[dp++] = b;
418
                    }
419
                }
420
            }
421
            if (sp < end) {               // 1 or 2 leftover bytes
422
                int b0 = src[sp++] & 0xff;
423
                dst[dp++] = (byte)base64[b0 >> 2];
424
                if (sp == end) {
425
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
426
                    if (doPadding) {
427
                        dst[dp++] = '=';
428
                        dst[dp++] = '=';
429
                    }
430
                } else {
431
                    int b1 = src[sp++] & 0xff;
432
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
433
                    dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
434
                    if (doPadding) {
435
                        dst[dp++] = '=';
436
                    }
437
                }
438
            }
439
            return dp;
440
        }
441
    }
442

  
443
    /**
444
     * This class implements a decoder for decoding byte data using the
445
     * Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
446
     *
447
     * <p> The Base64 padding character {@code '='} is accepted and
448
     * interpreted as the end of the encoded byte data, but is not
449
     * required. So if the final unit of the encoded byte data only has
450
     * two or three Base64 characters (without the corresponding padding
451
     * character(s) padded), they are decoded as if followed by padding
452
     * character(s). If there is a padding character present in the
453
     * final unit, the correct number of padding character(s) must be
454
     * present, otherwise {@code IllegalArgumentException} (
455
     * {@code IOException} when reading from a Base64 stream) is thrown
456
     * during decoding.
457
     *
458
     * <p> Instances of {@link Decoder} class are safe for use by
459
     * multiple concurrent threads.
460
     *
461
     * <p> Unless otherwise noted, passing a {@code null} argument to
462
     * a method of this class will cause a
463
     * {@link java.lang.NullPointerException NullPointerException} to
464
     * be thrown.
465
     *
466
     * @see     Encoder
467
     * @since   1.8
468
     */
469
    public static class Decoder {
470

  
471
        private final boolean isURL;
472
        private final boolean isMIME;
473

  
474
        private Decoder(boolean isURL, boolean isMIME) {
475
            this.isURL = isURL;
476
            this.isMIME = isMIME;
477
        }
478

  
479
        /**
480
         * Lookup table for decoding unicode characters drawn from the
481
         * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
482
         * their 6-bit positive integer equivalents.  Characters that
483
         * are not in the Base64 alphabet but fall within the bounds of
484
         * the array are encoded to -1.
485
         *
486
         */
487
        private static final int[] fromBase64 = new int[256];
488
        static {
489
            Arrays.fill(fromBase64, -1);
490
            for (int i = 0; i < Encoder.toBase64.length; i++)
491
                fromBase64[Encoder.toBase64[i]] = i;
492
            fromBase64['='] = -2;
493
        }
494

  
495
        /**
496
         * Lookup table for decoding "URL and Filename safe Base64 Alphabet"
497
         * as specified in Table2 of the RFC 4648.
498
         */
499
        private static final int[] fromBase64URL = new int[256];
500

  
501
        static {
502
            Arrays.fill(fromBase64URL, -1);
503
            for (int i = 0; i < Encoder.toBase64URL.length; i++)
504
                fromBase64URL[Encoder.toBase64URL[i]] = i;
505
            fromBase64URL['='] = -2;
506
        }
507

  
508
        static final Decoder RFC4648         = new Decoder(false, false);
509
        static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
510
        static final Decoder RFC2045         = new Decoder(false, true);
511

  
512
        /**
513
         * Decodes all bytes from the input byte array using the {@link Base64}
514
         * encoding scheme, writing the results into a newly-allocated output
515
         * byte array. The returned byte array is of the length of the resulting
516
         * bytes.
517
         *
518
         * @param   src
519
         *          the byte array to decode
520
         *
521
         * @return  A newly-allocated byte array containing the decoded bytes.
522
         *
523
         * @throws  IllegalArgumentException
524
         *          if {@code src} is not in valid Base64 scheme
525
         */
526
        public byte[] decode(byte[] src) {
527
            byte[] dst = new byte[outLength(src, 0, src.length)];
528
            int ret = decode0(src, 0, src.length, dst);
529
            if (ret != dst.length) {
530
                dst = Arrays.copyOf(dst, ret);
531
            }
532
            return dst;
533
        }
534

  
535
        /**
536
         * Decodes a Base64 encoded String into a newly-allocated byte array
537
         * using the {@link Base64} encoding scheme.
538
         *
539
         * <p> An invocation of this method has exactly the same effect as invoking
540
         * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))}
541
         *
542
         * @param   src
543
         *          the string to decode
544
         *
545
         * @return  A newly-allocated byte array containing the decoded bytes.
546
         *
547
         * @throws  IllegalArgumentException
548
         *          if {@code src} is not in valid Base64 scheme
549
         */
550
        public byte[] decode(String src) {
551
            return decode(src.getBytes(StandardCharsets.ISO_8859_1));
552
        }
553

  
554
        /**
555
         * Decodes all bytes from the input byte array using the {@link Base64}
556
         * encoding scheme, writing the results into the given output byte array,
557
         * starting at offset 0.
558
         *
559
         * <p> It is the responsibility of the invoker of this method to make
560
         * sure the output byte array {@code dst} has enough space for decoding
561
         * all bytes from the input byte array. No bytes will be be written to
562
         * the output byte array if the output byte array is not big enough.
563
         *
564
         * <p> If the input byte array is not in valid Base64 encoding scheme
565
         * then some bytes may have been written to the output byte array before
566
         * IllegalargumentException is thrown.
567
         *
568
         * @param   src
569
         *          the byte array to decode
570
         * @param   dst
571
         *          the output byte array
572
         *
573
         * @return  The number of bytes written to the output byte array
574
         *
575
         * @throws  IllegalArgumentException
576
         *          if {@code src} is not in valid Base64 scheme, or {@code dst}
577
         *          does not have enough space for decoding all input bytes.
578
         */
579
        public int decode(byte[] src, byte[] dst) {
580
            int len = outLength(src, 0, src.length);
581
            if (dst.length < len)
582
                throw new IllegalArgumentException(
583
                    "Output byte array is too small for decoding all input bytes");
584
            return decode0(src, 0, src.length, dst);
585
        }
586

  
587
        /**
588
         * Decodes all bytes from the input byte buffer using the {@link Base64}
589
         * encoding scheme, writing the results into a newly-allocated ByteBuffer.
590
         *
591
         * <p> Upon return, the source buffer's position will be updated to
592
         * its limit; its limit will not have been changed. The returned
593
         * output buffer's position will be zero and its limit will be the
594
         * number of resulting decoded bytes
595
         *
596
         * <p> {@code IllegalArgumentException} is thrown if the input buffer
597
         * is not in valid Base64 encoding scheme. The position of the input
598
         * buffer will not be advanced in this case.
599
         *
600
         * @param   buffer
601
         *          the ByteBuffer to decode
602
         *
603
         * @return  A newly-allocated byte buffer containing the decoded bytes
604
         *
605
         * @throws  IllegalArgumentException
606
         *          if {@code src} is not in valid Base64 scheme.
607
         */
608
        public ByteBuffer decode(ByteBuffer buffer) {
609
            int pos0 = buffer.position();
610
            try {
611
                byte[] src;
612
                int sp, sl;
613
                if (buffer.hasArray()) {
614
                    src = buffer.array();
615
                    sp = buffer.arrayOffset() + buffer.position();
616
                    sl = buffer.arrayOffset() + buffer.limit();
617
                    buffer.position(buffer.limit());
618
                } else {
619
                    src = new byte[buffer.remaining()];
620
                    buffer.get(src);
621
                    sp = 0;
622
                    sl = src.length;
623
                }
624
                byte[] dst = new byte[outLength(src, sp, sl)];
625
                return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
626
            } catch (IllegalArgumentException iae) {
627
                buffer.position(pos0);
628
                throw iae;
629
            }
630
        }
631

  
632
        /**
633
         * Returns an input stream for decoding {@link Base64} encoded byte stream.
634
         *
635
         * <p> The {@code read}  methods of the returned {@code InputStream} will
636
         * throw {@code IOException} when reading bytes that cannot be decoded.
637
         *
638
         * <p> Closing the returned input stream will close the underlying
639
         * input stream.
640
         *
641
         * @param   is
642
         *          the input stream
643
         *
644
         * @return  the input stream for decoding the specified Base64 encoded
645
         *          byte stream
646
         */
647
        public InputStream wrap(InputStream is) {
648
            Objects.requireNonNull(is);
649
            return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
650
        }
651

  
652
        private int outLength(byte[] src, int sp, int sl) {
653
            int[] base64 = isURL ? fromBase64URL : fromBase64;
654
            int paddings = 0;
655
            int len = sl - sp;
656
            if (len == 0)
657
                return 0;
658
            if (len < 2) {
659
                if (isMIME && base64[0] == -1)
660
                    return 0;
661
                throw new IllegalArgumentException(
662
                    "Input byte[] should at least have 2 bytes for base64 bytes");
663
            }
664
            if (isMIME) {
665
                // scan all bytes to fill out all non-alphabet. a performance
666
                // trade-off of pre-scan or Arrays.copyOf
667
                int n = 0;
668
                while (sp < sl) {
669
                    int b = src[sp++] & 0xff;
670
                    if (b == '=') {
671
                        len -= (sl - sp + 1);
672
                        break;
673
                    }
674
                    if ((b = base64[b]) == -1)
675
                        n++;
676
                }
677
                len -= n;
678
            } else {
679
                if (src[sl - 1] == '=') {
680
                    paddings++;
681
                    if (src[sl - 2] == '=')
682
                        paddings++;
683
                }
684
            }
685
            if (paddings == 0 && (len & 0x3) !=  0)
686
                paddings = 4 - (len & 0x3);
687
            return 3 * ((len + 3) / 4) - paddings;
688
        }
689

  
690
        private int decode0(byte[] src, int sp, int sl, byte[] dst) {
691
            int[] base64 = isURL ? fromBase64URL : fromBase64;
692
            int dp = 0;
693
            int bits = 0;
694
            int shiftto = 18;       // pos of first byte of 4-byte atom
695
            while (sp < sl) {
696
                int b = src[sp++] & 0xff;
697
                if ((b = base64[b]) < 0) {
698
                    if (b == -2) {         // padding byte '='
699
                        // =     shiftto==18 unnecessary padding
700
                        // x=    shiftto==12 a dangling single x
701
                        // x     to be handled together with non-padding case
702
                        // xx=   shiftto==6&&sp==sl missing last =
703
                        // xx=y  shiftto==6 last is not =
704
                        if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||
705
                            shiftto == 18) {
706
                            throw new IllegalArgumentException(
707
                                "Input byte array has wrong 4-byte ending unit");
708
                        }
709
                        break;
710
                    }
711
                    if (isMIME)    // skip if for rfc2045
712
                        continue;
713
                    else
714
                        throw new IllegalArgumentException(
715
                            "Illegal base64 character " +
716
                            Integer.toString(src[sp - 1], 16));
717
                }
718
                bits |= (b << shiftto);
719
                shiftto -= 6;
720
                if (shiftto < 0) {
721
                    dst[dp++] = (byte)(bits >> 16);
722
                    dst[dp++] = (byte)(bits >>  8);
723
                    dst[dp++] = (byte)(bits);
724
                    shiftto = 18;
725
                    bits = 0;
726
                }
727
            }
728
            // reached end of byte array or hit padding '=' characters.
729
            if (shiftto == 6) {
730
                dst[dp++] = (byte)(bits >> 16);
731
            } else if (shiftto == 0) {
732
                dst[dp++] = (byte)(bits >> 16);
733
                dst[dp++] = (byte)(bits >>  8);
734
            } else if (shiftto == 12) {
735
                // dangling single "x", incorrectly encoded.
736
                throw new IllegalArgumentException(
737
                    "Last unit does not have enough valid bits");
738
            }
739
            // anything left is invalid, if is not MIME.
740
            // if MIME, ignore all non-base64 character
741
            while (sp < sl) {
742
                if (isMIME && base64[src[sp++]] < 0)
743
                    continue;
744
                throw new IllegalArgumentException(
745
                    "Input byte array has incorrect ending byte at " + sp);
746
            }
747
            return dp;
748
        }
749
    }
750

  
751
    /*
752
     * An output stream for encoding bytes into the Base64.
753
     */
754
    private static class EncOutputStream extends FilterOutputStream {
755

  
756
        private int leftover = 0;
757
        private int b0, b1, b2;
758
        private boolean closed = false;
759

  
760
        private final char[] base64;    // byte->base64 mapping
761
        private final byte[] newline;   // line separator, if needed
762
        private final int linemax;
763
        private final boolean doPadding;// whether or not to pad
764
        private int linepos = 0;
765

  
766
        EncOutputStream(OutputStream os, char[] base64,
767
                        byte[] newline, int linemax, boolean doPadding) {
768
            super(os);
769
            this.base64 = base64;
770
            this.newline = newline;
771
            this.linemax = linemax;
772
            this.doPadding = doPadding;
773
        }
774

  
775
        @Override
776
        public void write(int b) throws IOException {
777
            byte[] buf = new byte[1];
778
            buf[0] = (byte)(b & 0xff);
779
            write(buf, 0, 1);
780
        }
781

  
782
        private void checkNewline() throws IOException {
783
            if (linepos == linemax) {
784
                out.write(newline);
785
                linepos = 0;
786
            }
787
        }
788

  
789
        @Override
790
        public void write(byte[] b, int off, int len) throws IOException {
791
            if (closed)
792
                throw new IOException("Stream is closed");
793
            if (off < 0 || len < 0 || off + len > b.length)
794
                throw new ArrayIndexOutOfBoundsException();
795
            if (len == 0)
796
                return;
797
            if (leftover != 0) {
798
                if (leftover == 1) {
799
                    b1 = b[off++] & 0xff;
800
                    len--;
801
                    if (len == 0) {
802
                        leftover++;
803
                        return;
804
                    }
805
                }
806
                b2 = b[off++] & 0xff;
807
                len--;
808
                checkNewline();
809
                out.write(base64[b0 >> 2]);
810
                out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
811
                out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
812
                out.write(base64[b2 & 0x3f]);
813
                linepos += 4;
814
            }
815
            int nBits24 = len / 3;
816
            leftover = len - (nBits24 * 3);
817
            while (nBits24-- > 0) {
818
                checkNewline();
819
                int bits = (b[off++] & 0xff) << 16 |
820
                           (b[off++] & 0xff) <<  8 |
821
                           (b[off++] & 0xff);
822
                out.write(base64[(bits >>> 18) & 0x3f]);
823
                out.write(base64[(bits >>> 12) & 0x3f]);
824
                out.write(base64[(bits >>> 6)  & 0x3f]);
825
                out.write(base64[bits & 0x3f]);
826
                linepos += 4;
827
           }
828
            if (leftover == 1) {
829
                b0 = b[off++] & 0xff;
830
            } else if (leftover == 2) {
831
                b0 = b[off++] & 0xff;
832
                b1 = b[off++] & 0xff;
833
            }
834
        }
835

  
836
        @Override
837
        public void close() throws IOException {
838
            if (!closed) {
839
                closed = true;
840
                if (leftover == 1) {
841
                    checkNewline();
842
                    out.write(base64[b0 >> 2]);
843
                    out.write(base64[(b0 << 4) & 0x3f]);
844
                    if (doPadding) {
845
                        out.write('=');
846
                        out.write('=');
847
                    }
848
                } else if (leftover == 2) {
849
                    checkNewline();
850
                    out.write(base64[b0 >> 2]);
851
                    out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
852
                    out.write(base64[(b1 << 2) & 0x3f]);
853
                    if (doPadding) {
854
                       out.write('=');
855
                    }
856
                }
857
                leftover = 0;
858
                out.close();
859
            }
860
        }
861
    }
862

  
863
    /*
864
     * An input stream for decoding Base64 bytes
865
     */
866
    private static class DecInputStream extends InputStream {
867

  
868
        private final InputStream is;
869
        private final boolean isMIME;
870
        private final int[] base64;      // base64 -> byte mapping
871
        private int bits = 0;            // 24-bit buffer for decoding
872
        private int nextin = 18;         // next available "off" in "bits" for input;
873
                                         // -> 18, 12, 6, 0
874
        private int nextout = -8;        // next available "off" in "bits" for output;
875
                                         // -> 8, 0, -8 (no byte for output)
876
        private boolean eof = false;
877
        private boolean closed = false;
878

  
879
        DecInputStream(InputStream is, int[] base64, boolean isMIME) {
880
            this.is = is;
881
            this.base64 = base64;
882
            this.isMIME = isMIME;
883
        }
884

  
885
        private byte[] sbBuf = new byte[1];
886

  
887
        @Override
888
        public int read() throws IOException {
889
            return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
890
        }
891

  
892
        @Override
893
        public int read(byte[] b, int off, int len) throws IOException {
894
            if (closed)
895
                throw new IOException("Stream is closed");
896
            if (eof && nextout < 0)    // eof and no leftover
897
                return -1;
898
            if (off < 0 || len < 0 || len > b.length - off)
899
                throw new IndexOutOfBoundsException();
900
            int oldOff = off;
901
            if (nextout >= 0) {       // leftover output byte(s) in bits buf
902
                do {
903
                    if (len == 0)
904
                        return off - oldOff;
905
                    b[off++] = (byte)(bits >> nextout);
906
                    len--;
907
                    nextout -= 8;
908
                } while (nextout >= 0);
909
                bits = 0;
910
            }
911
            while (len > 0) {
912
                int v = is.read();
913
                if (v == -1) {
914
                    eof = true;
915
                    if (nextin != 18) {
916
                        if (nextin == 12)
917
                            throw new IOException("Base64 stream has one un-decoded dangling byte.");
918
                        // treat ending xx/xxx without padding character legal.
919
                        // same logic as v == '=' below
920
                        b[off++] = (byte)(bits >> (16));
921
                        len--;
922
                        if (nextin == 0) {           // only one padding byte
923
                            if (len == 0) {          // no enough output space
924
                                bits >>= 8;          // shift to lowest byte
925
                                nextout = 0;
926
                            } else {
927
                                b[off++] = (byte) (bits >>  8);
928
                            }
929
                        }
930
                    }
931
                    if (off == oldOff)
932
                        return -1;
933
                    else
934
                        return off - oldOff;
935
                }
936
                if (v == '=') {                  // padding byte(s)
937
                    // =     shiftto==18 unnecessary padding
938
                    // x=    shiftto==12 dangling x, invalid unit
939
                    // xx=   shiftto==6 && missing last '='
940
                    // xx=y  or last is not '='
941
                    if (nextin == 18 || nextin == 12 ||
942
                        nextin == 6 && is.read() != '=') {
943
                        throw new IOException("Illegal base64 ending sequence:" + nextin);
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff