Can we reuse the same Properties object we have throughout Karaf ? It's the following: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/properties/Properties.java?revision=1076377&view=markup This one is also ordered, but has the benefit (which would not be used here) to be able to read / change properties file while preserving comments, formatting and variable substitution.
On Fri, Mar 18, 2011 at 01:47, <[email protected]> wrote: > Author: djencks > Date: Fri Mar 18 00:47:16 2011 > New Revision: 1082775 > > URL: http://svn.apache.org/viewvc?rev=1082775&view=rev > Log: > KARAF-424, KARAF-523 when constructing startup.properties, use maven-style > ids and preserve ordering > > Added: > > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java > (with props) > Modified: > > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java > > Modified: > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java > URL: > http://svn.apache.org/viewvc/karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java?rev=1082775&r1=1082774&r2=1082775&view=diff > ============================================================================== > --- > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java > (original) > +++ > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java > Fri Mar 18 00:47:16 2011 > @@ -28,6 +28,8 @@ import java.io.OutputStream; > import java.net.URI; > import java.util.Collection; > import java.util.EnumSet; > +import java.util.LinkedHashMap; > +import java.util.Map; > import java.util.Properties; > import java.util.Set; > > @@ -165,7 +167,7 @@ public class InstallKarsMojo extends Moj > } > } else { > getLog().info("Installing feature to system and > startup.properties"); > - Properties startupProperties = new Properties(); > + OrderedProperties startupProperties = new > OrderedProperties(); > if (startupPropertiesFile.exists()) { > InputStream in = new > FileInputStream(startupPropertiesFile); > try { > @@ -194,14 +196,14 @@ public class InstallKarsMojo extends Moj > for (Bundle bundle: feature.getBundle()) { > String location = bundle.getLocation(); > String startLevel = > Integer.toString(bundle.getStartLevel()); > - bits = location.toString().split("[:/]"); > - if (bits.length < 4) { > - getLog().warn("bad bundle: " + location); > - } else { > - Artifact bundleArtifact = > factory.createArtifact(bits[1], bits[2], bits[3], null, bits.length == 4? > "jar": bits[4]); > - String bundlePath = layout.pathOf(bundleArtifact); > +// bits = location.toString().split("[:/]"); > +// if (bits.length < 4) { > +// getLog().warn("bad bundle: " + location); > +// } else { > +// Artifact bundleArtifact = > factory.createArtifact(bits[1], bits[2], bits[3], null, bits.length == 4? > "jar": bits[4]); > + String bundlePath = location.startsWith("mvn:")? > location.substring("mvn:".length()).replaceAll("/", ":"): location; > + //layout.pathOf(bundleArtifact); > startupProperties.put(bundlePath, startLevel); > - } > } > } > > @@ -266,4 +268,5 @@ public class InstallKarsMojo extends Moj > return null; > } > } > + > } > > Added: > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java > URL: > http://svn.apache.org/viewvc/karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java?rev=1082775&view=auto > ============================================================================== > --- > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java > (added) > +++ > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java > Fri Mar 18 00:47:16 2011 > @@ -0,0 +1,769 @@ > +/* > + * Licensed to the Apache Software Foundation (ASF) under one or more > + * contributor license agreements. See the NOTICE file distributed with > + * this work for additional information regarding copyright ownership. > + * The ASF licenses this file to You under the Apache License, Version 2.0 > + * (the "License"); you may not use this file except in compliance with > + * the License. You may obtain a copy of the License at > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > + > +package org.apache.karaf.tooling.features; > + > +import java.io.IOException; > +import java.io.InputStream; > +import java.io.BufferedInputStream; > +import java.io.OutputStream; > +import java.io.OutputStreamWriter; > +import java.io.PrintStream; > +import java.io.PrintWriter; > +import java.io.StringReader; > +import java.nio.charset.Charset; > +import java.nio.charset.IllegalCharsetNameException; > +import java.nio.charset.UnsupportedCharsetException; > +import java.security.AccessController; > +import java.security.PrivilegedAction; > +import java.util.Date; > +import java.util.Enumeration; > +import java.util.InvalidPropertiesFormatException; > +import java.util.Iterator; > +import java.util.LinkedHashMap; > +import java.util.Map; > +import java.util.Properties; > + > +import javax.xml.parsers.DocumentBuilder; > +import javax.xml.parsers.DocumentBuilderFactory; > +import javax.xml.parsers.ParserConfigurationException; > + > +import org.xml.sax.EntityResolver; > +import org.xml.sax.ErrorHandler; > +import org.xml.sax.InputSource; > +import org.xml.sax.SAXException; > +import org.xml.sax.SAXParseException; > + > +import org.w3c.dom.Document; > +import org.w3c.dom.Element; > +import org.w3c.dom.NodeList; > + > +//import org.apache.harmony.luni.internal.nls.Messages; > +//import org.apache.harmony.luni.util.PriviAction; > + > +/** > + * This is a copy of harmony's Properties implementation modified to extend > LinkedHashMap instead of Hashtable > + * The default property stuff doesn't work. > + * > + * A {@code Properties} object is a {@code Hashtable} where the keys and > values > + * must be {@code String}s. Each property can have a default > + * {@code Properties} list which specifies the default > + * values to be used when a given key is not found in this {@code Properties} > + * instance. > + * > + * @see LinkedHashMap > + * @see java.lang.System#getProperties > + */ > +public class OrderedProperties extends LinkedHashMap<Object, Object> { > + > + private static final long serialVersionUID = 4112578634029874840L; > + > + private transient DocumentBuilder builder = null; > + > + private static final String PROP_DTD_NAME = > "http://java.sun.com/dtd/properties.dtd"; > + > + private static final String PROP_DTD = "<?xml version=\"1.0\" > encoding=\"UTF-8\"?>" > + + " <!ELEMENT properties (comment?, entry*) >" > + + " <!ATTLIST properties version CDATA #FIXED \"1.0\" >" > + + " <!ELEMENT comment (#PCDATA) >" > + + " <!ELEMENT entry (#PCDATA) >" > + + " <!ATTLIST entry key CDATA #REQUIRED >"; > + > + /** > + * The default values for keys not found in this {@code Properties} > + * instance. > + */ > + protected Properties defaults; > + > + private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3, > + KEY_DONE = 4, IGNORE = 5; > + > + /** > + * Constructs a new {@code Properties} object. > + */ > + public OrderedProperties() { > + super(); > + } > + > + /** > + * Constructs a new {@code Properties} object using the specified default > + * {@code Properties}. > + * > + * @param properties > + * the default {@code Properties}. > + */ > + public OrderedProperties(Properties properties) { > + defaults = properties; > + } > + > + private void dumpString(StringBuilder buffer, String string, boolean > key) { > + int i = 0; > + if (!key && i < string.length() && string.charAt(i) == ' ') { > + buffer.append("\\ "); //$NON-NLS-1$ > + i++; > + } > + > + for (; i < string.length(); i++) { > + char ch = string.charAt(i); > + switch (ch) { > + case '\t': > + buffer.append("\\t"); //$NON-NLS-1$ > + break; > + case '\n': > + buffer.append("\\n"); //$NON-NLS-1$ > + break; > + case '\f': > + buffer.append("\\f"); //$NON-NLS-1$ > + break; > + case '\r': > + buffer.append("\\r"); //$NON-NLS-1$ > + break; > + default: > + if ("\\#!=:".indexOf(ch) >= 0 || (key && ch == ' ')) { > + buffer.append('\\'); > + } > + if (ch >= ' ' && ch <= '~') { > + buffer.append(ch); > + } else { > + String hex = Integer.toHexString(ch); > + buffer.append("\\u"); //$NON-NLS-1$ > + for (int j = 0; j < 4 - hex.length(); j++) { > + buffer.append("0"); //$NON-NLS-1$ > + } > + buffer.append(hex); > + } > + } > + } > + } > + > + /** > + * Searches for the property with the specified name. If the property is > not > + * found, the default {@code Properties} are checked. If the property is > not > + * found in the default {@code Properties}, {@code null} is returned. > + * > + * @param name > + * the name of the property to find. > + * @return the named property value, or {@code null} if it can't be > found. > + */ > + public String getProperty(String name) { > + Object result = super.get(name); > + String property = result instanceof String ? (String) result : null; > + if (property == null && defaults != null) { > + property = defaults.getProperty(name); > + } > + return property; > + } > + > + /** > + * Searches for the property with the specified name. If the property is > not > + * found, it looks in the default {@code Properties}. If the property is > not > + * found in the default {@code Properties}, it returns the specified > + * default. > + * > + * @param name > + * the name of the property to find. > + * @param defaultValue > + * the default value. > + * @return the named property value. > + */ > + public String getProperty(String name, String defaultValue) { > + Object result = super.get(name); > + String property = result instanceof String ? (String) result : null; > + if (property == null && defaults != null) { > + property = defaults.getProperty(name); > + } > + if (property == null) { > + return defaultValue; > + } > + return property; > + } > + > + /** > + * Lists the mappings in this {@code Properties} to the specified > + * {@code PrintStream} in a > + * human readable form. > + * > + * @param out > + * the {@code PrintStream} to write the content to in human > readable > + * form. > + */ > + public void list(PrintStream out) { > + if (out == null) { > + throw new NullPointerException(); > + } > + StringBuilder buffer = new StringBuilder(80); > + Iterator<?> keys = keySet().iterator(); > + while (keys.hasNext()) { > + String key = (String) keys.next(); > + buffer.append(key); > + buffer.append('='); > + String property = (String) super.get(key); > + Properties def = defaults; > + if (property == null) { > + property = def.getProperty(key); > + } > + if (property.length() > 40) { > + buffer.append(property.substring(0, 37)); > + buffer.append("..."); //$NON-NLS-1$ > + } else { > + buffer.append(property); > + } > + out.println(buffer.toString()); > + buffer.setLength(0); > + } > + } > + > + /** > + * Lists the mappings in this {@code Properties} to the specified > + * {@code PrintWriter} in a > + * human readable form. > + * > + * @param writer > + * the {@code PrintWriter} to write the content to in human > + * readable form. > + */ > + public void list(PrintWriter writer) { > + if (writer == null) { > + throw new NullPointerException(); > + } > + StringBuilder buffer = new StringBuilder(80); > + Iterator<?> keys = keySet().iterator(); > + while (keys.hasNext()) { > + String key = (String) keys.next(); > + buffer.append(key); > + buffer.append('='); > + String property = (String) super.get(key); > + Properties def = defaults; > + if (property == null) { > + property = def.getProperty(key); > + } > + if (property.length() > 40) { > + buffer.append(property.substring(0, 37)); > + buffer.append("..."); //$NON-NLS-1$ > + } else { > + buffer.append(property); > + } > + writer.println(buffer.toString()); > + buffer.setLength(0); > + } > + } > + > + /** > + * Loads properties from the specified {@code InputStream}. The encoding > is > + * ISO8859-1. The {@code Properties} file is interpreted according to the > + * following rules: > + * <ul> > + * <li>Empty lines are ignored.</li> > + * <li>Lines starting with either a "#" or a "!" are comment lines and > are > + * ignored.</li> > + * <li>A backslash at the end of the line escapes the following newline > + * character ("\r", "\n", "\r\n"). If there's a whitespace after the > + * backslash it will just escape that whitespace instead of concatenating > + * the lines. This does not apply to comment lines.</li> > + * <li>A property line consists of the key, the space between the key and > + * the value, and the value. The key goes up to the first whitespace, > "=" or > + * ":" that is not escaped. The space between the key and the value > contains > + * either one whitespace, one "=" or one ":" and any number of additional > + * whitespaces before and after that character. The value starts with the > + * first character after the space between the key and the value.</li> > + * <li>Following escape sequences are recognized: "\ ", "\\", "\r", "\n", > + * "\!", "\#", "\t", "\b", "\f", and "\uXXXX" (unicode > character).</li> > + * </ul> > + * > + * @param in > + * the {@code InputStream}. > + * @throws IOException > + * if error occurs during reading from the {@code > InputStream}. > + */ > + @SuppressWarnings("fallthrough") > + public synchronized void load(InputStream in) throws IOException { > + if (in == null) { > + throw new NullPointerException(); > + } > + int mode = NONE, unicode = 0, count = 0; > + char nextChar, buf[] = new char[40]; > + int offset = 0, keyLength = -1, intVal; > + boolean firstChar = true; > + BufferedInputStream bis = new BufferedInputStream(in); > + > + while (true) { > + intVal = bis.read(); > + if (intVal == -1) { > + // if mode is UNICODE but has less than 4 hex digits, should > + // throw an IllegalArgumentException > + // luni.08=Invalid Unicode sequence: expected format \\uxxxx > + if (mode == UNICODE && count < 4) { > + throw new IllegalArgumentException("Invalid Unicode > sequence: expected format \\\\uxxxx"); //$NON-NLS-1$ > + } > + // if mode is SLASH and no data is read, should append > '\u0000' > + // to buf > + if (mode == SLASH) { > + buf[offset++] = '\u0000'; > + } > + break; > + } > + nextChar = (char) (intVal & 0xff); > + > + if (offset == buf.length) { > + char[] newBuf = new char[buf.length * 2]; > + System.arraycopy(buf, 0, newBuf, 0, offset); > + buf = newBuf; > + } > + if (mode == UNICODE) { > + int digit = Character.digit(nextChar, 16); > + if (digit >= 0) { > + unicode = (unicode << 4) + digit; > + if (++count < 4) { > + continue; > + } > + } else if (count <= 4) { > + // luni.09=Invalid Unicode sequence: illegal character > + throw new IllegalArgumentException("Invalid Unicode > sequence: illegal character"); //$NON-NLS-1$ > + } > + mode = NONE; > + buf[offset++] = (char) unicode; > + if (nextChar != '\n') { > + continue; > + } > + } > + if (mode == SLASH) { > + mode = NONE; > + switch (nextChar) { > + case '\r': > + mode = CONTINUE; // Look for a following \n > + continue; > + case '\n': > + mode = IGNORE; // Ignore whitespace on the next line > + continue; > + case 'b': > + nextChar = '\b'; > + break; > + case 'f': > + nextChar = '\f'; > + break; > + case 'n': > + nextChar = '\n'; > + break; > + case 'r': > + nextChar = '\r'; > + break; > + case 't': > + nextChar = '\t'; > + break; > + case 'u': > + mode = UNICODE; > + unicode = count = 0; > + continue; > + } > + } else { > + switch (nextChar) { > + case '#': > + case '!': > + if (firstChar) { > + while (true) { > + intVal = bis.read(); > + if (intVal == -1) { > + break; > + } > + // & 0xff not required > + nextChar = (char) intVal; > + if (nextChar == '\r' || nextChar == '\n') { > + break; > + } > + } > + continue; > + } > + break; > + case '\n': > + if (mode == CONTINUE) { // Part of a \r\n sequence > + mode = IGNORE; // Ignore whitespace on the next line > + continue; > + } > + // fall into the next case > + case '\r': > + mode = NONE; > + firstChar = true; > + if (offset > 0 || (offset == 0 && keyLength == 0)) { > + if (keyLength == -1) { > + keyLength = offset; > + } > + String temp = new String(buf, 0, offset); > + put(temp.substring(0, keyLength), temp > + .substring(keyLength)); > + } > + keyLength = -1; > + offset = 0; > + continue; > + case '\\': > + if (mode == KEY_DONE) { > + keyLength = offset; > + } > + mode = SLASH; > + continue; > + case ':': > + case '=': > + if (keyLength == -1) { // if parsing the key > + mode = NONE; > + keyLength = offset; > + continue; > + } > + break; > + } > + if (Character.isWhitespace(nextChar)) { > + if (mode == CONTINUE) { > + mode = IGNORE; > + } > + // if key length == 0 or value length == 0 > + if (offset == 0 || offset == keyLength || mode == > IGNORE) { > + continue; > + } > + if (keyLength == -1) { // if parsing the key > + mode = KEY_DONE; > + continue; > + } > + } > + if (mode == IGNORE || mode == CONTINUE) { > + mode = NONE; > + } > + } > + firstChar = false; > + if (mode == KEY_DONE) { > + keyLength = offset; > + mode = NONE; > + } > + buf[offset++] = nextChar; > + } > + if (keyLength == -1 && offset > 0) { > + keyLength = offset; > + } > + if (keyLength >= 0) { > + String temp = new String(buf, 0, offset); > + put(temp.substring(0, keyLength), temp.substring(keyLength)); > + } > + } > + > + /** > + * Returns all of the property names that this {@code Properties} object > + * contains. > + * > + * @return an {@code Enumeration} containing the names of all properties > + * that this {@code Properties} object contains. > + */ > +// public Enumeration<?> propertyNames() { > +// if (defaults == null) { > +// return keySet().; > +// } > +// > +// Hashtable<Object, Object> set = new Hashtable<Object, > Object>(defaults > +// .size() > +// + size()); > +// Enumeration<?> keys = defaults.propertyNames(); > +// while (keys.hasMoreElements()) { > +// set.put(keys.nextElement(), set); > +// } > +// keys = keys(); > +// while (keys.hasMoreElements()) { > +// set.put(keys.nextElement(), set); > +// } > +// return set.keys(); > +// } > + > + /** > + * Saves the mappings in this {@code Properties} to the specified {@code > + * OutputStream}, putting the specified comment at the beginning. The > output > + * from this method is suitable for being read by the > + * {@link #load(InputStream)} method. > + * > + * @param out the {@code OutputStream} to write to. > + * @param comment the comment to add at the beginning. > + * @throws ClassCastException if the key or value of a mapping is not a > + * String. > + * @deprecated This method ignores any {@code IOException} thrown while > + * writing -- use {@link #store} instead for better exception > + * handling. > + */ > + @Deprecated > + public void save(OutputStream out, String comment) { > + try { > + store(out, comment); > + } catch (IOException e) { > + } > + } > + > + /** > + * Maps the specified key to the specified value. If the key already > exists, > + * the old value is replaced. The key and value cannot be {@code null}. > + * > + * @param name > + * the key. > + * @param value > + * the value. > + * @return the old value mapped to the key, or {@code null}. > + */ > + public Object setProperty(String name, String value) { > + return put(name, value); > + } > + > + private static String lineSeparator; > + > + /** > + * Stores the mappings in this {@code Properties} to the specified {@code > + * OutputStream}, putting the specified comment at the beginning. The > output > + * from this method is suitable for being read by the > + * {@link #load(InputStream)} method. > + * > + * @param out the {@code OutputStream} to write to. > + * @param comment the comment to put at the beginning. > + * @throws IOException if an error occurs during the write to the {@code > + * OutputStream}. > + * @throws ClassCastException if the key or value of a mapping is not a > + * {@code String}. > + */ > + public synchronized void store(OutputStream out, String comment) > + throws IOException { > + if (lineSeparator == null) { > + lineSeparator = AccessController > + .doPrivileged(new PrivilegedAction<String>(){ > + > + public String run() { > + return System.getProperty("line.separator"); > + } > + }); //$NON-NLS-1$ > + } > + > + StringBuilder buffer = new StringBuilder(200); > + OutputStreamWriter writer = new OutputStreamWriter(out, > "ISO8859_1"); //$NON-NLS-1$ > + if (comment != null) { > + writer.write("#"); //$NON-NLS-1$ > + writer.write(comment); > + writer.write(lineSeparator); > + } > + writer.write("#"); //$NON-NLS-1$ > + writer.write(new Date().toString()); > + writer.write(lineSeparator); > + > + for (Map.Entry<Object, Object> entry : entrySet()) { > + String key = (String) entry.getKey(); > + dumpString(buffer, key, true); > + buffer.append('='); > + dumpString(buffer, (String) entry.getValue(), false); > + buffer.append(lineSeparator); > + writer.write(buffer.toString()); > + buffer.setLength(0); > + } > + writer.flush(); > + } > + > + /** > + * Loads the properties from an {@code InputStream} containing the > + * properties in XML form. The XML document must begin with (and conform > to) > + * following DOCTYPE: > + * > + * <pre> > + * <!DOCTYPE properties SYSTEM > "http://java.sun.com/dtd/properties.dtd"> > + * </pre> > + * > + * Also the content of the XML data must satisfy the DTD but the xml is > not > + * validated against it. The DTD is not loaded from the SYSTEM ID. After > + * this method returns the InputStream is not closed. > + * > + * @param in the InputStream containing the XML document. > + * @throws IOException in case an error occurs during a read operation. > + * @throws InvalidPropertiesFormatException if the XML data is not a > valid > + * properties file. > + */ > + public synchronized void loadFromXML(InputStream in) throws IOException, > + InvalidPropertiesFormatException { > + if (in == null) { > + throw new NullPointerException(); > + } > + > + if (builder == null) { > + DocumentBuilderFactory factory = DocumentBuilderFactory > + .newInstance(); > + factory.setValidating(true); > + > + try { > + builder = factory.newDocumentBuilder(); > + } catch (ParserConfigurationException e) { > + throw new Error(e); > + } > + > + builder.setErrorHandler(new ErrorHandler() { > + public void warning(SAXParseException e) throws SAXException > { > + throw e; > + } > + > + public void error(SAXParseException e) throws SAXException { > + throw e; > + } > + > + public void fatalError(SAXParseException e) throws > SAXException { > + throw e; > + } > + }); > + > + builder.setEntityResolver(new EntityResolver() { > + public InputSource resolveEntity(String publicId, > + String systemId) throws SAXException, IOException { > + if (systemId.equals(PROP_DTD_NAME)) { > + InputSource result = new InputSource(new > StringReader( > + PROP_DTD)); > + result.setSystemId(PROP_DTD_NAME); > + return result; > + } > + throw new SAXException("Invalid DOCTYPE declaration: " > + + systemId); > + } > + }); > + } > + > + try { > + Document doc = builder.parse(in); > + NodeList entries = doc.getElementsByTagName("entry"); > + if (entries == null) { > + return; > + } > + int entriesListLength = entries.getLength(); > + > + for (int i = 0; i < entriesListLength; i++) { > + Element entry = (Element) entries.item(i); > + String key = entry.getAttribute("key"); > + String value = entry.getTextContent(); > + > + /* > + * key != null & value != null but key or(and) value can be > + * empty String > + */ > + put(key, value); > + } > + } catch (IOException e) { > + throw e; > + } catch (SAXException e) { > + throw new InvalidPropertiesFormatException(e); > + } > + } > + > + /** > + * Writes all properties stored in this instance into the {@code > + * OutputStream} in XML representation. The DOCTYPE is > + * > + * <pre> > + * <!DOCTYPE properties SYSTEM > "http://java.sun.com/dtd/properties.dtd"> > + * </pre> > + * > + * If the comment is null, no comment is added to the output. UTF-8 is > used > + * as the encoding. The {@code OutputStream} is not closed at the end. A > + * call to this method is the same as a call to {@code storeToXML(os, > + * comment, "UTF-8")}. > + * > + * @param os the {@code OutputStream} to write to. > + * @param comment the comment to add. If null, no comment is added. > + * @throws IOException if an error occurs during writing to the output. > + */ > + public void storeToXML(OutputStream os, String comment) throws > IOException { > + storeToXML(os, comment, "UTF-8"); //$NON-NLS-1$ > + } > + > + /** > + * Writes all properties stored in this instance into the {@code > + * OutputStream} in XML representation. The DOCTYPE is > + * > + * <pre> > + * <!DOCTYPE properties SYSTEM > "http://java.sun.com/dtd/properties.dtd"> > + * </pre> > + * > + * If the comment is null, no comment is added to the output. The > parameter > + * {@code encoding} defines which encoding should be used. The {@code > + * OutputStream} is not closed at the end. > + * > + * @param os the {@code OutputStream} to write to. > + * @param comment the comment to add. If null, no comment is added. > + * @param encoding the code identifying the encoding that should be used > to > + * write into the {@code OutputStream}. > + * @throws IOException if an error occurs during writing to the output. > + */ > + public synchronized void storeToXML(OutputStream os, String comment, > + String encoding) throws IOException { > + > + if (os == null || encoding == null) { > + throw new NullPointerException(); > + } > + > + /* > + * We can write to XML file using encoding parameter but note that > some > + * aliases for encodings are not supported by the XML parser. Thus we > + * have to know canonical name for encoding used to store data in XML > + * since the XML parser must recognize encoding name used to store > data. > + */ > + > + String encodingCanonicalName; > + try { > + encodingCanonicalName = Charset.forName(encoding).name(); > + } catch (IllegalCharsetNameException e) { > + System.out.println("Warning: encoding name " + encoding > + + " is illegal, using UTF-8 as default encoding"); > + encodingCanonicalName = "UTF-8"; > + } catch (UnsupportedCharsetException e) { > + System.out.println("Warning: encoding " + encoding > + + " is not supported, using UTF-8 as default encoding"); > + encodingCanonicalName = "UTF-8"; > + } > + > + PrintStream printStream = new PrintStream(os, false, > + encodingCanonicalName); > + > + printStream.print("<?xml version=\"1.0\" encoding=\""); > + printStream.print(encodingCanonicalName); > + printStream.println("\"?>"); > + > + printStream.print("<!DOCTYPE properties SYSTEM \""); > + printStream.print(PROP_DTD_NAME); > + printStream.println("\">"); > + > + printStream.println("<properties>"); > + > + if (comment != null) { > + printStream.print("<comment>"); > + printStream.print(substitutePredefinedEntries(comment)); > + printStream.println("</comment>"); > + } > + > + for (Map.Entry<Object, Object> entry : entrySet()) { > + String keyValue = (String) entry.getKey(); > + String entryValue = (String) entry.getValue(); > + printStream.print("<entry key=\""); > + printStream.print(substitutePredefinedEntries(keyValue)); > + printStream.print("\">"); > + printStream.print(substitutePredefinedEntries(entryValue)); > + printStream.println("</entry>"); > + } > + printStream.println("</properties>"); > + printStream.flush(); > + } > + > + private String substitutePredefinedEntries(String s) { > + > + /* > + * substitution for predefined character entities to use them safely > in > + * XML > + */ > + return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll( > + ">", ">").replaceAll("\u0027", "'").replaceAll("\"", > + """); > + } > +} > > Propchange: > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java > ------------------------------------------------------------------------------ > svn:eol-style = native > > Propchange: > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java > ------------------------------------------------------------------------------ > svn:keywords = Date Revision > > Propchange: > karaf/trunk/tooling/features-maven-plugin/src/main/java/org/apache/karaf/tooling/features/OrderedProperties.java > ------------------------------------------------------------------------------ > svn:mime-type = text/plain > > > -- Cheers, Guillaume Nodet ------------------------ Blog: http://gnodet.blogspot.com/ ------------------------ Open Source SOA http://fusesource.com
