/*******************************************************************************
* Copyright (c) 2010, 2015 EclipseSource and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* EclipseSource - initial API and implementation
******************************************************************************/
package org.eclipse.rap.clientbuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import org.mozilla.javascript.Token;
import com.yahoo.platform.yui.compressor.JavaScriptToken;
public final class StringReplacer {
private final HashMap<String, Integer> stringMap = new HashMap<>();
private List<String> strings;
public void discoverStrings( TokenList tokens ) {
if( strings != null ) {
throw new IllegalStateException( "Can not add strings after computing indexes" );
}
int length = tokens.size();
for( int pos = 0; pos < length; pos++ ) {
if( isReplacableString( tokens, pos ) ) {
String value = tokens.getToken( pos ).getValue();
Integer count = stringMap.get( value );
if( count == null ) {
stringMap.put( value, new Integer( 1 ) );
} else {
stringMap.put( value, new Integer( count.intValue() + 1 ) );
}
}
}
}
public void replaceStrings( TokenList tokens ) {
ensureStringListCreated();
int length = tokens.size();
for( int pos = length - 1; pos >= 0; pos-- ) {
if( isReplacableString( tokens, pos ) ) {
String string = tokens.getToken( pos ).getValue();
int index = getIndex( string );
if( index != -1 ) {
JavaScriptToken[] replacement = createTokensForArrayAccess( "_", index );
tokens.replaceToken( pos, replacement );
}
}
}
}
public void optimize() {
ensureStringListCreated();
for( int i = strings.size() - 1; i >= 0; i-- ) {
String string = strings.get( i );
if( !isWorthReplacing( string ) ) {
strings.remove( i );
}
}
}
public String[] getStrings() {
ensureStringListCreated();
String[] result = new String[ strings.size() ];
strings.toArray( result );
return result;
}
private boolean isWorthReplacing( String string ) {
int freq = getFrequency( string );
return freq > 1 && ( string.length() + 2 ) * ( freq - 1 ) > freq * 6;
}
private int getFrequency( String string ) {
Integer frequency = stringMap.get( string );
return frequency == null ? 0 : frequency.intValue();
}
private int getIndex( String string ) {
return strings.indexOf( string );
}
private void ensureStringListCreated() {
if( strings == null ) {
strings = new ArrayList<>( stringMap.keySet() );
Comparator<String> comparator = new Comparator<String>() {
@Override
public int compare( String string1, String string2 ) {
int freq1 = getFrequency( string1 );
int freq2 = getFrequency( string2 );
return freq1 < freq2 ? 1 : ( freq1 == freq2 ? 0 : -1 );
}
};
Collections.sort( strings, comparator );
}
}
private static JavaScriptToken[] createTokensForArrayAccess( String arrayName,
int index )
{
JavaScriptToken[] replacement = new JavaScriptToken[] {
new JavaScriptToken( Token.NAME, arrayName ),
new JavaScriptToken( Token.LB, "[" ),
new JavaScriptToken( Token.NUMBER, String.valueOf( index ) ),
new JavaScriptToken( Token.RB, "]" )
};
return replacement;
}
private static boolean isReplacableString( TokenList tokens, int pos ) {
boolean result = false;
JavaScriptToken token = tokens.getToken( pos );
if( isString( token ) ) {
JavaScriptToken nextToken = tokens.getToken( pos + 1 );
if( !isColonInObjectLiteral( nextToken ) ) {
result = true;
}
}
return result;
}
static boolean isString( JavaScriptToken token ) {
return token != null && token.getType() == Token.STRING;
}
static boolean isColonInObjectLiteral( JavaScriptToken token ) {
return token != null && token.getType() == Token.OBJECTLIT;
}
}