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.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(final Properties defaultValues ) 058 { 059 super( defaultValues ); 060 } 061 062 /** 063 * {@inheritDoc} 064 */ 065 @Override 066 public synchronized void load(final InputStream inStream ) throws IOException 067 { 068 // Load the file itself into a string 069 m_propertyString = FileUtil.readContents( inStream, StandardCharsets.ISO_8859_1.name() ); 070 071 // Now load it into the properties object as normal 072 super.load( new ByteArrayInputStream( m_propertyString.getBytes(StandardCharsets.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(final 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(StandardCharsets.ISO_8859_1) ) ); 088 } 089 090 /** 091 * {@inheritDoc} 092 */ 093 @Override 094 public synchronized Object setProperty(final String key, final String value ) 095 { 096 return put(key, value); 097 } 098 099 /** 100 * {@inheritDoc} 101 */ 102 @Override 103 public synchronized void store(final OutputStream out, final String comments ) throws IOException 104 { 105 final byte[] bytes = m_propertyString.getBytes( StandardCharsets.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(final Object arg0, final 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(final Map< ? , ? > arg0 ) 128 { 129 // Shove all of the entries into the property string 130 for (final Entry<?, ?> value : arg0.entrySet()) { 131 @SuppressWarnings("unchecked") final Entry<Object, Object> entry = (Entry<Object, Object>) value; 132 writeProperty(entry.getKey(), entry.getValue()); 133 } 134 135 // Call the superclass method 136 super.putAll(arg0); 137 } 138 139 /** 140 * {@inheritDoc} 141 */ 142 @Override 143 public synchronized Object remove(final Object key ) 144 { 145 // Remove from the property string 146 deleteProperty( key ); 147 148 // Call the superclass method 149 return super.remove(key); 150 } 151 152 /** 153 * {@inheritDoc} 154 */ 155 @Override 156 public synchronized String toString() 157 { 158 return m_propertyString; 159 } 160 161 private void deleteProperty(final Object arg0 ) 162 { 163 // Get key and value 164 if ( arg0 == null ) 165 { 166 throw new IllegalArgumentException( "Key cannot be null." ); 167 } 168 final String key = arg0.toString(); 169 170 // Iterate through each line and replace anything matching our key 171 int idx = 0; 172 while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) ) 173 { 174 final int prevret = m_propertyString.lastIndexOf( "\n", idx ); 175 if ( prevret != -1 ) 176 { 177 // Commented lines are skipped 178 if ( m_propertyString.charAt( prevret + 1 ) == '#' ) 179 { 180 idx += key.length(); 181 continue; 182 } 183 } 184 185 // If "=" present, delete the entire line 186 final int eqsign = m_propertyString.indexOf( "=", idx ); 187 if ( eqsign != -1 ) 188 { 189 final int ret = m_propertyString.indexOf( "\n", eqsign ); 190 m_propertyString = TextUtil.replaceString( m_propertyString, prevret, ret, "" ); 191 return; 192 } 193 } 194 } 195 196 private void writeProperty(final Object arg0, Object arg1 ) 197 { 198 // Get key and value 199 if ( arg0 == null ) 200 { 201 throw new IllegalArgumentException( "Key cannot be null." ); 202 } 203 if ( arg1 == null ) 204 { 205 arg1 = ""; 206 } 207 final String key = arg0.toString(); 208 final String value = TextUtil.native2Ascii( arg1.toString() ); 209 210 // Iterate through each line and replace anything matching our key 211 int idx = 0; 212 while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) ) 213 { 214 final int prevret = m_propertyString.lastIndexOf( "\n", idx ); 215 if ( prevret != -1 ) 216 { 217 // Commented lines are skipped 218 if ( m_propertyString.charAt( prevret + 1 ) == '#' ) 219 { 220 idx += key.length(); 221 continue; 222 } 223 } 224 225 // If "=" present, replace everything in line after it 226 final int eqsign = m_propertyString.indexOf( "=", idx ); 227 if ( eqsign != -1 ) 228 { 229 int ret = m_propertyString.indexOf( "\n", eqsign ); 230 if ( ret == -1 ) 231 { 232 ret = m_propertyString.length(); 233 } 234 m_propertyString = TextUtil.replaceString( m_propertyString, eqsign + 1, ret, value ); 235 return; 236 } 237 } 238 239 // If it was not found, we'll add it to the end. 240 m_propertyString += "\n" + key + " = " + value + "\n"; 241 } 242 243}