/******************************************************************************* * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * *******************************************************************************/ package com.liferay.ide.project.core.modules.templates; import com.liferay.ide.core.util.CoreUtil; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Properties; /** * @author Simon Jiang */ public class BndProperties extends Properties { private static final long serialVersionUID = 1L; private Charset UTF8 = Charset.forName( "UTF-8" ); private final int PAGE_SIZE = 4096; private List<String> keyList = new ArrayList<String>(); public void addKeyList( final String key ) { if ( keyList.contains( key ) ) { return; } keyList.add( key ); } public void addValue( final String key, final BndPropertiesValue newBdValue ) { BndPropertiesValue bdValue = (BndPropertiesValue) get( key ); addKeyList( key ); if( bdValue != null ) { String originalValue = bdValue.getOriginalValue(); if( originalValue != null && !CoreUtil.isNullOrEmpty( originalValue ) ) { StringBuilder formatedValueBuilder = new StringBuilder( bdValue.getFormatedValue() ); StringBuilder originalValueBuilder = new StringBuilder( bdValue.getOriginalValue() ); String newOriginalValue = newBdValue.getOriginalValue(); String[] newOriginalValues = newOriginalValue.split( "," ); BndPropertiesValue inputValue = new BndPropertiesValue(); for( String newValue : newOriginalValues ) { if( originalValue.contains( newValue ) ) { continue; } if( bdValue.isMultiLine() ) { formatedValueBuilder.append( ",\\" + System.getProperty( "line.separator" ) ); formatedValueBuilder.append( "\t" + newValue ); inputValue.setFormatedValue( formatedValueBuilder.toString() ); } else { originalValueBuilder.append( "," ).append( newValue ); inputValue.setFormatedValue( originalValueBuilder.toString() ); } } put( key, inputValue ); } else { put( key, newBdValue ); } } else { put( key, newBdValue ); } } private String convert( byte[] buffer, Charset charset ) throws IOException { CharsetDecoder decoder = charset.newDecoder(); ByteBuffer bb = ByteBuffer.wrap( buffer ); CharBuffer cb = CharBuffer.allocate( buffer.length * 4 ); CoderResult result = decoder.decode( bb, cb, true ); if( !result.isError() ) { return new String( cb.array(), 0, cb.position() ); } throw new CharacterCodingException(); } public String getReader( Reader a ) throws IOException { StringWriter sw = new StringWriter(); char[] buffer = new char[PAGE_SIZE]; int size = a.read( buffer ); while( size > 0 ) { sw.write( buffer, 0, size ); size = a.read( buffer ); } return sw.toString(); } private void formatForOutput( String str, StringBuilder buffer, boolean key ) { if( key ) { buffer.setLength( 0 ); buffer.ensureCapacity( str.length() ); } else buffer.ensureCapacity( buffer.length() + str.length() ); boolean head = true; int size = str.length(); for( int i = 0; i < size; i++ ) { char c = str.charAt( i ); switch( c ) { case '\n': buffer.append( "\\n" ); break; case '\r': buffer.append( "\\r" ); break; case '\t': buffer.append( "\\t" ); break; case ' ': buffer.append( head ? "\\ " : " " ); break; // case '\\': case '!': case '#': // case '=': // case ':': buffer.append( '\\' ).append( c ); break; default: if( c < ' ' || c > '~' ) { String hex = Integer.toHexString( c ); buffer.append( "\\u0000".substring( 0, 6 - hex.length() ) ); buffer.append( hex ); } else buffer.append( c ); } if( c != ' ' ) head = key; } } @Override public void store( OutputStream out, String header ) throws IOException { PrintWriter writer = new PrintWriter( new OutputStreamWriter( out ) ); if( header != null ) { writer.println( "#" + header ); writer.println( "#" + Calendar.getInstance().getTime() ); } StringBuilder s = new StringBuilder(); // Reuse the same buffer. for( String keyString : keyList ) { formatForOutput( (String) keyString, s, true ); s.append( ": " ); Object value = get(keyString); if ( value instanceof BndPropertiesValue ) { final BndPropertiesValue bndValue = (BndPropertiesValue) value; writer.println( s.append( bndValue.getFormatedValue() ) ); } else { formatForOutput( (String) value, s, false ); writer.println( s ); } } writer.flush(); } public void load( File bndFile ) throws IOException { try(InputStream in = new FileInputStream( bndFile )) { load( in ); } catch( Exception e ) { } } @Override public void load( InputStream inStream ) throws IOException { // The spec says that the file must be encoded using ISO-8859-1. BufferedReader reader = new BufferedReader( new InputStreamReader( inStream, "ISO-8859-1" ) ); String buffer; while( ( buffer = reader.readLine() ) != null ) { String line = convert( buffer.getBytes(), UTF8 ); BndPropertiesValue bnd = new BndPropertiesValue(); char c = 0; int pos = 0; // Leading whitespaces must be deleted first. while( pos < line.length() && Character.isWhitespace( c = line.charAt( pos ) ) ) pos++; // If empty line or begins with a comment character, skip this line. if( ( line.length() - pos ) == 0 || line.charAt( pos ) == '#' || line.charAt( pos ) == '!' ) continue; // The characters up to the next Whitespace, ':', or '=' // describe the key. But look for escape sequences. // Try to short-circuit when there is no escape char. int start = pos; boolean needsEscape = line.indexOf( '\\', pos ) != -1; StringBuilder key = needsEscape ? new StringBuilder() : null; while( pos < line.length() && !Character.isWhitespace( c = line.charAt( pos++ ) ) && c != '=' && c != ':' ) { if( needsEscape && c == '\\' ) { if( pos == line.length() ) { // The line continues on the next line. If there // is no next line, just treat it as a key with an // empty value. line = reader.readLine(); if( line == null ) line = ""; pos = 0; while( pos < line.length() && Character.isWhitespace( c = line.charAt( pos ) ) ) pos++; } else { c = line.charAt( pos++ ); switch( c ) { case 'n': key.append( '\n' ); break; case 't': key.append( '\t' ); break; case 'r': key.append( '\r' ); break; case 'u': if( pos + 4 <= line.length() ) { char uni = (char) Integer.parseInt( line.substring( pos, pos + 4 ), 16 ); key.append( uni ); pos += 4; } // else throw exception? break; default: key.append( c ); break; } } } else if( needsEscape ) key.append( c ); } boolean isDelim = ( c == ':' || c == '=' ); String keyString; if( needsEscape ) keyString = key.toString(); else if( isDelim || Character.isWhitespace( c ) ) keyString = line.substring( start, pos - 1 ); else keyString = line.substring( start, pos ); while( pos < line.length() && Character.isWhitespace( c = line.charAt( pos ) ) ) pos++; if( !isDelim && ( c == ':' || c == '=' ) ) { pos++; while( pos < line.length() && Character.isWhitespace( c = line.charAt( pos ) ) ) pos++; } // Short-circuit if no escape chars found. if( !needsEscape ) { bnd.setOriginalValue( line.substring( pos ) ); bnd.setFormatedValue( line.substring( pos ) ); addKeyList( keyString ); put( keyString, bnd ); continue; } // Escape char found so iterate through the rest of the line. StringBuilder element = new StringBuilder( line.length() - pos ); StringBuilder formatedElement = new StringBuilder(line.substring( pos )); //formatedElement.append( line ); while( pos < line.length() ) { c = line.charAt( pos++ ); if( c == '\\' ) { if( pos == line.length() ) { bnd.setMultiLine( true ); formatedElement.append( System.getProperty( "line.separator" ) ); // The line continues on the next line. line = reader.readLine(); formatedElement.append( line ); // We might have seen a backslash at the end of // the file. The JDK ignores the backslash in // this case, so we follow for compatibility. if( line == null ) break; pos = 0; while( pos < line.length() && Character.isWhitespace( c = line.charAt( pos ) ) ) pos++; element.ensureCapacity( line.length() - pos + element.length() ); } else { c = line.charAt( pos++ ); switch( c ) { case 'n': element.append( '\n' ); formatedElement.append( '\n' ); break; case 't': element.append( '\t' ); formatedElement.append( '\t' ); break; case 'r': element.append( '\r' ); formatedElement.append( '\r' ); break; case 'u': if( pos + 4 <= line.length() ) { char uni = (char) Integer.parseInt( line.substring( pos, pos + 4 ), 16 ); element.append( uni ); pos += 4; } // else throw exception? break; default: element.append( c ); break; } } } else { element.append( c ); } } bnd.setOriginalValue( element.toString() ); bnd.setFormatedValue( formatedElement.toString() ); addKeyList( keyString ); put( keyString, bnd ); } } }