Skip to main content
aboutsummaryrefslogblamecommitdiffstats
blob: baf7d3068478a260ed79197da1ba7e510613ef27 (plain) (tree)





















































































































































































                                                                                                                                                                            
/*******************************************************************************
 * Copyright (c) 2005, 2009 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0, which accompanies this distribution
 * and is available at http://www.eclipse.org/legal/epl-v10.html.
 * 
 * Contributors:
 *     Oracle - initial API and implementation
 ******************************************************************************/
package org.eclipse.jpt.common.utility.internal;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

/**
 * This encoder will replace any of a specified set of characters with an XML
 * "character reference": '/' => "/"
 */
public final class XMLStringEncoder {

	/** The set of characters to be converted into XML character references. */
	private final char[] chars;

	/** Cache the value of the highest character in the set above. */
	private final char maxChar;


	// ********** constructors/initialization **********

	/**
	 * Construct an encoder that converts the specified set of characters
	 * into XML character references.
	 */
	public XMLStringEncoder(char[] chars) {
		super();
		if (chars == null) {
			throw new NullPointerException();
		}
		// the ampersand must be included since it is the escape character
		if (ArrayTools.contains(chars, '&')) {
			this.chars = chars;
		} else {
			this.chars = ArrayTools.add(chars, '&');
		}
		this.maxChar = this.calculateMaxInvalidFileNameChar();
	}

	/**
	 * Calculate the maximum value of the set of characters to be converted
	 * into XML character references. This will be used to short-circuit the
	 * search for a character in the set.
	 * @see #charIsToBeEncoded(char)
	 */
	private char calculateMaxInvalidFileNameChar() {
		char[] localChars = this.chars;
		char max = 0;
		for (int i = localChars.length; i-- > 0; ) {
			char c = localChars[i];
			if (max < c) {
				max = c;
			}
		}
		return max;
	}


	// ********** API **********

	/**
	 * Return the specified string with any characters in the set
	 * replaced with XML character references.
	 */
	public String encode(String s) {
		int len = s.length();
		// allow for a few encoded characters
		StringBuilder sb = new StringBuilder(len + 20);
		for (int i = 0; i < len; i++) {
			this.appendCharacterTo(s.charAt(i), sb);
		}
		return sb.toString();
	}

	/**
	 * Return the specified string with any XML character references
	 * replaced by the characters themselves.
	 */
	public String decode(String s) {
		StringBuilder sb = new StringBuilder(s.length());
		StringBuilder temp = new StringBuilder();	// performance tweak
		this.decodeTo(new StringReader(s), sb, temp);
		return sb.toString();
	}


	// ********** internal methods **********

	/**
	 * Append the specified character to the string buffer,
	 * converting it to an XML character reference if necessary.
	 */
	private void appendCharacterTo(char c, StringBuilder sb) {
		if (this.charIsToBeEncoded(c)) {
			this.appendCharacterReferenceTo(c, sb);
		} else {
			sb.append(c);
		}
	}

	/**
	 * Return whether the specified character is one of the characters
	 * to be converted to XML character references.
	 */
	private boolean charIsToBeEncoded(char c) {
		return (c <= this.maxChar) && ArrayTools.contains(this.chars, c);
	}

	/**
	 * Append the specified character's XML character reference to the
	 * specified string buffer (e.g. '/' => "&#x2f;").
	 */
	private void appendCharacterReferenceTo(char c, StringBuilder sb) {
		sb.append("&#x"); //$NON-NLS-1$
		sb.append(Integer.toString(c, 16));
		sb.append(';');
	}

	private void decodeTo(Reader reader, StringBuilder sb, StringBuilder temp) {
		try {
			this.decodeTo_(reader, sb, temp);
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}

	private void decodeTo_(Reader reader, StringBuilder sb, StringBuilder temp) throws IOException {
		int c = reader.read();
		while (c != -1) {
			if (c == '&') {
				this.decodeCharacterReferenceTo(reader, sb, temp);
			} else {
				sb.append((char) c);
			}
			c = reader.read();
		}
		reader.close();
	}

	private void decodeCharacterReferenceTo(Reader reader, StringBuilder sb, StringBuilder temp) throws IOException {
		int c = reader.read();
		this.checkChar(c, '#');
		c = reader.read();
		this.checkChar(c, 'x');

		temp.setLength(0);  // re-use temp
		c = reader.read();
		while (c != ';') {
			this.checkEndOfStream(c);
			temp.append((char) c);
			c = reader.read();
		}
		String charValue = temp.toString();
		if (charValue.length() == 0) {
			throw new IllegalStateException("missing numeric string"); //$NON-NLS-1$
		}
		sb.append((char) Integer.parseInt(charValue, 16));
	}

	private void checkChar(int c, int expected) {
		this.checkEndOfStream(c);
		if (c != expected) {
			throw new IllegalStateException("expected '" + (char) expected + "', but encountered '" + (char) c + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
	}

	private void checkEndOfStream(int c) {
		if (c == -1) {
			throw new IllegalStateException("unexpected end of string"); //$NON-NLS-1$
		}
	}

}

Back to the top