001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.util; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.io.Reader; 026import java.util.Iterator; 027import java.util.Map; 028import java.util.Map.Entry; 029import java.util.Properties; 030 031/** 032 * Extends {@link java.util.Properties} by providing support for comment 033 * preservation. When the properties are written to disk, previous 034 * comments present in the file are preserved. 035 * @since 2.4.22 036 */ 037public class CommentedProperties extends Properties 038{ 039 private static final long serialVersionUID = 8057284636436329669L; 040 041 private String m_propertyString; 042 043 /** 044 * @see java.util.Properties#Properties() 045 */ 046 public CommentedProperties() 047 { 048 super(); 049 } 050 051 /** 052 * Creates new properties. 053 * 054 * @param defaultValues A list of default values, which are used if in subsequent gets 055 * a key is not found. 056 */ 057 public CommentedProperties( Properties defaultValues ) 058 { 059 super( defaultValues ); 060 } 061 062 /** 063 * {@inheritDoc} 064 */ 065 @Override 066 public synchronized void load( InputStream inStream ) throws IOException 067 { 068 // Load the file itself into a string 069 m_propertyString = FileUtil.readContents( inStream, "ISO-8859-1" ); 070 071 // Now load it into the properties object as normal 072 super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) ); 073 } 074 075 /** 076 * Loads properties from a file opened by a supplied Reader. 077 * 078 * @param in The reader to read properties from 079 * @throws IOException in case something goes wrong. 080 */ 081 @Override 082 public synchronized void load( Reader in ) throws IOException 083 { 084 m_propertyString = FileUtil.readContents( in ); 085 086 // Now load it into the properties object as normal 087 super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) ); 088 } 089 090 /** 091 * {@inheritDoc} 092 */ 093 @Override 094 public synchronized Object setProperty( String key, String value ) 095 { 096 return put(key, value); 097 } 098 099 /** 100 * {@inheritDoc} 101 */ 102 @Override 103 public synchronized void store( OutputStream out, String comments ) throws IOException 104 { 105 byte[] bytes = m_propertyString.getBytes("ISO-8859-1"); 106 FileUtil.copyContents( new ByteArrayInputStream( bytes ), out ); 107 out.flush(); 108 } 109 110 /** 111 * {@inheritDoc} 112 */ 113 @Override 114 public synchronized Object put( Object arg0, Object arg1 ) 115 { 116 // Write the property to the stored string 117 writeProperty( arg0, arg1 ); 118 119 // Return the result of from the superclass properties object 120 return super.put(arg0, arg1); 121 } 122 123 /** 124 * {@inheritDoc} 125 */ 126 @Override 127 public synchronized void putAll( Map< ? , ? > arg0 ) 128 { 129 // Shove all of the entries into the property string 130 for( Iterator< ? > it = arg0.entrySet().iterator(); it.hasNext(); ) 131 { 132 @SuppressWarnings("unchecked") 133 Entry< Object, Object > entry = ( Entry< Object, Object > )it.next(); 134 writeProperty( entry.getKey(), entry.getValue() ); 135 } 136 137 // Call the superclass method 138 super.putAll(arg0); 139 } 140 141 /** 142 * {@inheritDoc} 143 */ 144 @Override 145 public synchronized Object remove( Object key ) 146 { 147 // Remove from the property string 148 deleteProperty( key ); 149 150 // Call the superclass method 151 return super.remove(key); 152 } 153 154 /** 155 * {@inheritDoc} 156 */ 157 @Override 158 public synchronized String toString() 159 { 160 return m_propertyString; 161 } 162 163 private void deleteProperty( Object arg0 ) 164 { 165 // Get key and value 166 if ( arg0 == null ) 167 { 168 throw new IllegalArgumentException( "Key cannot be null." ); 169 } 170 String key = arg0.toString(); 171 172 // Iterate through each line and replace anything matching our key 173 int idx = 0; 174 while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) ) 175 { 176 int prevret = m_propertyString.lastIndexOf( "\n", idx ); 177 if ( prevret != -1 ) 178 { 179 // Commented lines are skipped 180 if ( m_propertyString.charAt( prevret + 1 ) == '#' ) 181 { 182 idx += key.length(); 183 continue; 184 } 185 } 186 187 // If "=" present, delete the entire line 188 int eqsign = m_propertyString.indexOf( "=", idx ); 189 if ( eqsign != -1 ) 190 { 191 int ret = m_propertyString.indexOf( "\n", eqsign ); 192 m_propertyString = TextUtil.replaceString( m_propertyString, prevret, ret, "" ); 193 return; 194 } 195 } 196 } 197 198 private void writeProperty( Object arg0, Object arg1 ) 199 { 200 // Get key and value 201 if ( arg0 == null ) 202 { 203 throw new IllegalArgumentException( "Key cannot be null." ); 204 } 205 if ( arg1 == null ) 206 { 207 arg1 = ""; 208 } 209 String key = arg0.toString(); 210 String value = TextUtil.native2Ascii( arg1.toString() ); 211 212 // Iterate through each line and replace anything matching our key 213 int idx = 0; 214 while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) ) 215 { 216 int prevret = m_propertyString.lastIndexOf( "\n", idx ); 217 if ( prevret != -1 ) 218 { 219 // Commented lines are skipped 220 if ( m_propertyString.charAt( prevret + 1 ) == '#' ) 221 { 222 idx += key.length(); 223 continue; 224 } 225 } 226 227 // If "=" present, replace everything in line after it 228 int eqsign = m_propertyString.indexOf( "=", idx ); 229 if ( eqsign != -1 ) 230 { 231 int ret = m_propertyString.indexOf( "\n", eqsign ); 232 if ( ret == -1 ) 233 { 234 ret = m_propertyString.length(); 235 } 236 m_propertyString = TextUtil.replaceString( m_propertyString, eqsign + 1, ret, value ); 237 return; 238 } 239 } 240 241 // If it was not found, we'll add it to the end. 242 m_propertyString += "\n" + key + " = " + value + "\n"; 243 } 244 245}