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 */ 019 package org.apache.wiki.util; 020 021 import java.io.ByteArrayInputStream; 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.OutputStream; 025 import java.io.Reader; 026 import java.util.Iterator; 027 import java.util.Map; 028 import java.util.Map.Entry; 029 import 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 */ 037 public 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 public synchronized void load( Reader in ) throws IOException 082 { 083 m_propertyString = FileUtil.readContents( in ); 084 085 // Now load it into the properties object as normal 086 super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) ); 087 } 088 089 /** 090 * {@inheritDoc} 091 */ 092 @Override 093 public synchronized Object setProperty( String key, String value ) 094 { 095 return put(key, value); 096 } 097 098 /** 099 * {@inheritDoc} 100 */ 101 @Override 102 public synchronized void store( OutputStream out, String comments ) throws IOException 103 { 104 byte[] bytes = m_propertyString.getBytes("ISO-8859-1"); 105 FileUtil.copyContents( new ByteArrayInputStream( bytes ), out ); 106 out.flush(); 107 } 108 109 /** 110 * {@inheritDoc} 111 */ 112 @Override 113 public synchronized Object put( Object arg0, Object arg1 ) 114 { 115 // Write the property to the stored string 116 writeProperty( arg0, arg1 ); 117 118 // Return the result of from the superclass properties object 119 return super.put(arg0, arg1); 120 } 121 122 /** 123 * {@inheritDoc} 124 */ 125 @SuppressWarnings("unchecked") 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 Entry entry = (Entry)it.next(); 133 writeProperty( entry.getKey(), entry.getValue() ); 134 } 135 136 // Call the superclass method 137 super.putAll(arg0); 138 } 139 140 /** 141 * {@inheritDoc} 142 */ 143 @Override 144 public synchronized Object remove( Object key ) 145 { 146 // Remove from the property string 147 deleteProperty( key ); 148 149 // Call the superclass method 150 return super.remove(key); 151 } 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public synchronized String toString() 158 { 159 return m_propertyString; 160 } 161 162 private void deleteProperty( Object arg0 ) 163 { 164 // Get key and value 165 if ( arg0 == null ) 166 { 167 throw new IllegalArgumentException( "Key cannot be null." ); 168 } 169 String key = arg0.toString(); 170 171 // Iterate through each line and replace anything matching our key 172 int idx = 0; 173 while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) ) 174 { 175 int prevret = m_propertyString.lastIndexOf( "\n", idx ); 176 if ( prevret != -1 ) 177 { 178 // Commented lines are skipped 179 if ( m_propertyString.charAt( prevret + 1 ) == '#' ) 180 { 181 idx += key.length(); 182 continue; 183 } 184 } 185 186 // If "=" present, delete the entire line 187 int eqsign = m_propertyString.indexOf( "=", idx ); 188 if ( eqsign != -1 ) 189 { 190 int ret = m_propertyString.indexOf( "\n", eqsign ); 191 m_propertyString = TextUtil.replaceString( m_propertyString, prevret, ret, "" ); 192 return; 193 } 194 } 195 } 196 197 private void writeProperty( Object arg0, Object arg1 ) 198 { 199 // Get key and value 200 if ( arg0 == null ) 201 { 202 throw new IllegalArgumentException( "Key cannot be null." ); 203 } 204 if ( arg1 == null ) 205 { 206 arg1 = ""; 207 } 208 String key = arg0.toString(); 209 String value = TextUtil.native2Ascii( arg1.toString() ); 210 211 // Iterate through each line and replace anything matching our key 212 int idx = 0; 213 while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) ) 214 { 215 int prevret = m_propertyString.lastIndexOf( "\n", idx ); 216 if ( prevret != -1 ) 217 { 218 // Commented lines are skipped 219 if ( m_propertyString.charAt( prevret + 1 ) == '#' ) 220 { 221 idx += key.length(); 222 continue; 223 } 224 } 225 226 // If "=" present, replace everything in line after it 227 int eqsign = m_propertyString.indexOf( "=", idx ); 228 if ( eqsign != -1 ) 229 { 230 int ret = m_propertyString.indexOf( "\n", eqsign ); 231 if ( ret == -1 ) 232 { 233 ret = m_propertyString.length(); 234 } 235 m_propertyString = TextUtil.replaceString( m_propertyString, eqsign + 1, ret, value ); 236 return; 237 } 238 } 239 240 // If it was not found, we'll add it to the end. 241 m_propertyString += "\n" + key + " = " + value + "\n"; 242 } 243 244 }