/******************************************************************************* * Copyright (c) 2007-2011, G. Weirich and Elexis * 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: * G. Weirich - initial API and implementation ******************************************************************************/ package ch.elexis.core.ui.views.textsystem; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.net.URL; import java.text.MessageFormat; import java.util.Properties; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Platform; import ch.elexis.core.ui.util.Log; /** * Diese Klasse ueberschreibt die load Funktionalitaet von java.util.Properties. Der Code entspricht * dem Code in java.util.Properties bis auf den kleinen Unterschied, dass ":" nicht als Trennzeichen * angesehen wird! * * @author nowhow * */ public abstract class AbstractProperties extends Properties { private static final long serialVersionUID = -9019950244305182074L; private static Log log = Log.get("AbstractProperties"); //$NON-NLS-1$ private final static String DIRECTORY = "rsc/platzhalter"; //$NON-NLS-1$ /** * Filename */ protected abstract String getFilename(); /** * Returns location of the properties file inside of the elexis plugin */ private String getPlatzhalterFilenamePath() throws IOException{ URL url = Platform.getBundle("ch.elexis.core.data").getEntry("/"); //$NON-NLS-1$ //$NON-NLS-2$ url = FileLocator.toFileURL(url); String bundleLocation = url.getPath(); return bundleLocation + File.separator + DIRECTORY + File.separator + getFilename(); } public AbstractProperties(){ super(); String filenamePath = getFilename(); FileReader reader = null; try { filenamePath = getPlatzhalterFilenamePath(); reader = new FileReader(filenamePath); load(reader); } catch (IOException e) { log.log(e, MessageFormat.format(Messages.AbstractProperties_message_FileNotFound, filenamePath), Log.ERRORS); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { ex.printStackTrace(); } } } } /** * Reads a property list (key and element pairs) from the input character stream in a simple * line-oriented format. * <p> * Properties are processed in terms of lines. There are two kinds of line, <i>natural lines</i> * and <i>logical lines</i>. A natural line is defined as a line of characters that is * terminated either by a set of line terminator characters (<code>\n</code> or <code>\r</code> * or <code>\r\n</code>) or by the end of the stream. A natural line may be either a blank line, * a comment line, or hold all or some of a key-element pair. A logical line holds all the data * of a key-element pair, which may be spread out across several adjacent natural lines by * escaping the line terminator sequence with a backslash character <code>\</code>. Note that a * comment line cannot be extended in this manner; every natural line that is a comment must * have its own comment indicator, as described below. Lines are read from input until the end * of the stream is reached. * * <p> * A natural line that contains only white space characters is considered blank and is ignored. * A comment line has an ASCII <code>'#'</code> or <code>'!'</code> as its first non-white space * character; comment lines are also ignored and do not encode key-element information. In * addition to line terminators, this format considers the characters space ( <code>' '</code>, * <code>'\u0020'</code>), tab (<code>'\t'</code>, <code>'\u0009'</code>), and form feed * (<code>'\f'</code>, <code>'\u000C'</code>) to be white space. * * <p> * If a logical line is spread across several natural lines, the backslash escaping the line * terminator sequence, the line terminator sequence, and any white space at the start of the * following line have no affect on the key or element values. The remainder of the discussion * of key and element parsing (when loading) will assume all the characters constituting the key * and element appear on a single natural line after line continuation characters have been * removed. Note that it is <i>not</i> sufficient to only examine the character preceding a line * terminator sequence to decide if the line terminator is escaped; there must be an odd number * of contiguous backslashes for the line terminator to be escaped. Since the input is processed * from left to right, a non-zero even number of 2<i>n</i> contiguous backslashes before a line * terminator (or elsewhere) encodes <i>n</i> backslashes after escape processing. * * <p> * The key contains all of the characters in the line starting with the first non-white space * character and up to, but not including, the first unescaped <code>'='</code>, * <code>':'</code>, or white space character other than a line terminator. All of these key * termination characters may be included in the key by escaping them with a preceding backslash * character; for example, * <p> * * <code>\:\=</code> * <p> * * would be the two-character key <code>":="</code>. Line terminator characters can be included * using <code>\r</code> and <code>\n</code> escape sequences. Any white space after the key is * skipped; if the first non-white space character after the key is <code>'='</code> or * <code>':'</code>, then it is ignored and any white space characters after it are also * skipped. All remaining characters on the line become part of the associated element string; * if there are no remaining characters, the element is the empty string * <code>""</code>. Once the raw character sequences constituting the key and element * are identified, escape processing is performed as described above. * * <p> * As an example, each of the following three lines specifies the key <code>"Truth"</code> and * the associated element value <code>"Beauty"</code>: * <p> * * <pre> * Truth = Beauty * Truth:Beauty * Truth :Beauty * </pre> * * As another example, the following three lines specify a single property: * <p> * * <pre> * fruits apple, banana, pear, \ * cantaloupe, watermelon, \ * kiwi, mango * </pre> * * The key is <code>"fruits"</code> and the associated element is: * <p> * * <pre> * "apple, banana, pear, cantaloupe, watermelon, kiwi, mango" * </pre> * * Note that a space appears before each <code>\</code> so that a space will appear after each * comma in the final result; the <code>\</code>, line terminator, and leading white space on * the continuation line are merely discarded and are <i>not</i> replaced by one or more other * characters. * <p> * As a third example, the line: * <p> * * <pre> * cheeses * </pre> * * specifies that the key is <code>"cheeses"</code> and the associated element is the empty * string <code>""</code>. * <p> * <p> * * <a name="unicodeescapes"></a> Characters in keys and elements can be represented in escape * sequences similar to those used for character and string literals (see <a href= * "http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3" >§3.3</a> and * <a href= "http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.6" * >§3.10.6</a> of the <i>Java Language Specification</i>). * * The differences from the character escape sequences and Unicode escapes used for characters * and strings are: * * <ul> * <li>Octal escapes are not recognized. * * <li>The character sequence <code>\b</code> does <i>not</i> represent a backspace character. * * <li>The method does not treat a backslash character, <code>\</code>, before a non-valid * escape character as an error; the backslash is silently dropped. For example, in a Java * string the sequence <code>"\z"</code> would cause a compile time error. In contrast, this * method silently drops the backslash. Therefore, this method treats the two character sequence * <code>"\b"</code> as equivalent to the single character <code>'b'</code>. * * <li>Escapes are not necessary for single and double quotes; however, by the rule above, * single and double quote characters preceded by a backslash still yield single and double * quote characters, respectively. * * <li>Only a single 'u' character is allowed in a Uniocde escape sequence. * * </ul> * <p> * The specified stream remains open after this method returns. * * @param reader * the input character stream. * @throws IOException * if an error occurred when reading from the input stream. * @throws IllegalArgumentException * if a malformed Unicode escape appears in the input. * @since 1.6 */ public synchronized void load(Reader reader) throws IOException{ load0(new LineReader(reader)); } /** * Reads a property list (key and element pairs) from the input byte stream. The input stream is * in a simple line-oriented format as specified in {@link #load(java.io.Reader) load(Reader)} * and is assumed to use the ISO 8859-1 character encoding; that is each byte is one Latin1 * character. Characters not in Latin1, and certain special characters, are represented in keys * and elements using <a href= * "http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3" >Unicode * escapes</a>. * <p> * The specified stream remains open after this method returns. * * @param inStream * the input stream. * @exception IOException * if an error occurred when reading from the input stream. * @throws IllegalArgumentException * if the input stream contains a malformed Unicode escape sequence. * @since 1.2 */ public synchronized void load(InputStream inStream) throws IOException{ load0(new LineReader(inStream)); } private void load0(LineReader lr) throws IOException{ char[] convtBuf = new char[1024]; int limit; int keyLen; int valueStart; char c; boolean hasSep; boolean precedingBackslash; while ((limit = lr.readLine()) >= 0) { c = 0; keyLen = 0; valueStart = limit; hasSep = false; // System.out.println("line=<" + new String(lineBuf, 0, limit) + // ">"); precedingBackslash = false; while (keyLen < limit) { c = lr.lineBuf[keyLen]; // need check if escaped. if ((c == '=') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break; } else if ((c == '\t' || c == '\f') && !precedingBackslash) { valueStart = keyLen + 1; break; } if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } keyLen++; } while (valueStart < limit) { c = lr.lineBuf[valueStart]; if (c != '\t' && c != '\f') { if (!hasSep && (c == '=')) { hasSep = true; } else { break; } } valueStart++; } String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); put(key, value); } } /* * Read in a "logical line" from an InputStream/Reader, skip all comment and blank lines and * filter out those leading whitespace characters ( , and ) from the beginning of a * "natural line". Method returns the char length of the "logical line" and stores the line in * "lineBuf". */ class LineReader { public LineReader(InputStream inStream){ this.inStream = inStream; inByteBuf = new byte[8192]; } public LineReader(Reader reader){ this.reader = reader; inCharBuf = new char[8192]; } byte[] inByteBuf; char[] inCharBuf; char[] lineBuf = new char[1024]; int inLimit = 0; int inOff = 0; InputStream inStream; Reader reader; int readLine() throws IOException{ int len = 0; char c = 0; boolean skipWhiteSpace = true; boolean isCommentLine = false; boolean isNewLine = true; boolean appendedLineBegin = false; boolean precedingBackslash = false; boolean skipLF = false; while (true) { if (inOff >= inLimit) { inLimit = (inStream == null) ? reader.read(inCharBuf) : inStream.read(inByteBuf); inOff = 0; if (inLimit <= 0) { if (len == 0 || isCommentLine) { return -1; } return len; } } if (inStream != null) { // The line below is equivalent to calling a // ISO8859-1 decoder. c = (char) (0xff & inByteBuf[inOff++]); } else { c = inCharBuf[inOff++]; } if (skipLF) { skipLF = false; if (c == '\n') { continue; } } if (skipWhiteSpace) { if (c == ' ' || c == '\t' || c == '\f') { continue; } if (!appendedLineBegin && (c == '\r' || c == '\n')) { continue; } skipWhiteSpace = false; appendedLineBegin = false; } if (isNewLine) { isNewLine = false; if (c == '#' || c == '!') { isCommentLine = true; continue; } } if (c != '\n' && c != '\r') { lineBuf[len++] = c; if (len == lineBuf.length) { int newLength = lineBuf.length * 2; if (newLength < 0) { newLength = Integer.MAX_VALUE; } char[] buf = new char[newLength]; System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); lineBuf = buf; } // flip the preceding backslash flag if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } } else { // reached EOL if (isCommentLine || len == 0) { isCommentLine = false; isNewLine = true; skipWhiteSpace = true; len = 0; continue; } if (inOff >= inLimit) { inLimit = (inStream == null) ? reader.read(inCharBuf) : inStream.read(inByteBuf); inOff = 0; if (inLimit <= 0) { return len; } } if (precedingBackslash) { len -= 1; // skip the leading whitespace characters in following // line skipWhiteSpace = true; appendedLineBegin = true; precedingBackslash = false; if (c == '\r') { skipLF = true; } } else { return len; } } } } } /* * Converts encoded \uxxxx to unicode chars and changes special saved chars to their * original forms */ private String loadConvert(char[] in, int off, int len, char[] convtBuf){ if (convtBuf.length < len) { int newLen = len * 2; if (newLen < 0) { newLen = Integer.MAX_VALUE; } convtBuf = new char[newLen]; } char aChar; char[] out = convtBuf; int outLen = 0; int end = off + len; while (off < end) { aChar = in[off++]; if (aChar == '\\') { aChar = in[off++]; if (aChar == 'u') { // Read the xxxx int value = 0; for (int i = 0; i < 4; i++) { aChar = in[off++]; switch (aChar) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': value = (value << 4) + aChar - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': value = (value << 4) + 10 + aChar - 'a'; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': value = (value << 4) + 10 + aChar - 'A'; break; default: throw new IllegalArgumentException("Malformed \\uxxxx encoding."); //$NON-NLS-1$ } } out[outLen++] = (char) value; } else { if (aChar == 't') aChar = '\t'; else if (aChar == 'r') aChar = '\r'; else if (aChar == 'n') aChar = '\n'; else if (aChar == 'f') aChar = '\f'; out[outLen++] = aChar; } } else { out[outLen++] = (char) aChar; } } return new String(out, 0, outLen); } }