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.nio.charset.StandardCharsets; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Properties; 031 032/** 033 * Extends {@link java.util.Properties} by providing support for comment 034 * preservation. When the properties are written to disk, previous 035 * comments present in the file are preserved. 036 * @since 2.4.22 037 */ 038public class CommentedProperties extends Properties 039{ 040 private static final long serialVersionUID = 8057284636436329669L; 041 042 private String m_propertyString; 043 044 /** 045 * @see java.util.Properties#Properties() 046 */ 047 public CommentedProperties() 048 { 049 super(); 050 } 051 052 /** 053 * Creates new properties. 054 * 055 * @param defaultValues A list of default values, which are used if in subsequent gets 056 * a key is not found. 057 */ 058 public CommentedProperties(final Properties defaultValues ) 059 { 060 super( defaultValues ); 061 } 062 063 /** 064 * {@inheritDoc} 065 */ 066 @Override 067 public synchronized void load(final InputStream inStream ) throws IOException 068 { 069 // Load the file itself into a string 070 m_propertyString = FileUtil.readContents( inStream, StandardCharsets.ISO_8859_1.name() ); 071 072 // Now load it into the properties object as normal 073 super.load( new ByteArrayInputStream( m_propertyString.getBytes(StandardCharsets.ISO_8859_1) ) ); 074 } 075 076 /** 077 * Loads properties from a file opened by a supplied Reader. 078 * 079 * @param in The reader to read properties from 080 * @throws IOException in case something goes wrong. 081 */ 082 @Override 083 public synchronized void load(final Reader in ) throws IOException 084 { 085 m_propertyString = FileUtil.readContents( in ); 086 087 // Now load it into the properties object as normal 088 super.load( new ByteArrayInputStream( m_propertyString.getBytes(StandardCharsets.ISO_8859_1) ) ); 089 } 090 091 /** 092 * {@inheritDoc} 093 */ 094 @Override 095 public synchronized Object setProperty(final String key, final String value ) 096 { 097 return put(key, value); 098 } 099 100 /** 101 * {@inheritDoc} 102 */ 103 @Override 104 public synchronized void store(final OutputStream out, final String comments ) throws IOException 105 { 106 final byte[] bytes = m_propertyString.getBytes(StandardCharsets.ISO_8859_1.name()); 107 FileUtil.copyContents( new ByteArrayInputStream( bytes ), out ); 108 out.flush(); 109 } 110 111 /** 112 * {@inheritDoc} 113 */ 114 @Override 115 public synchronized Object put(final Object arg0, final Object arg1 ) 116 { 117 // Write the property to the stored string 118 writeProperty( arg0, arg1 ); 119 120 // Return the result of from the superclass properties object 121 return super.put(arg0, arg1); 122 } 123 124 /** 125 * {@inheritDoc} 126 */ 127 @Override 128 public synchronized void putAll(final Map< ? , ? > arg0 ) 129 { 130 // Shove all of the entries into the property string 131 for(final Iterator< ? > it = arg0.entrySet().iterator(); it.hasNext(); ) 132 { 133 @SuppressWarnings("unchecked") final 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(final 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(final Object arg0 ) 164 { 165 // Get key and value 166 if ( arg0 == null ) 167 { 168 throw new IllegalArgumentException( "Key cannot be null." ); 169 } 170 final 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 final 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 final int eqsign = m_propertyString.indexOf( "=", idx ); 189 if ( eqsign != -1 ) 190 { 191 final 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(final 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 final String key = arg0.toString(); 210 final 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 final 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 final 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}