    001/*
    002 * Licensed under the Apache License, Version 2.0 (the "License");
    003 * you may not use this file except in compliance with the License.
    004 * You may obtain a copy of the License at
    005 *
    006 *     http://www.apache.org/licenses/LICENSE-2.0
    007 *
    008 * Unless required by applicable law or agreed to in writing, software
    009 * distributed under the License is distributed on an "AS IS" BASIS,
    010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    011 * See the License for the specific language governing permissions and
    012 * limitations under the License.
    013 */
    014package org.apache.wiki;
    015
    016import java.io.FileNotFoundException;
    017import java.io.IOException;
    018import java.io.InputStream;
    019import java.nio.charset.StandardCharsets;
    020import java.nio.file.Files;
    021import java.nio.file.Paths;
    022import java.util.ArrayList;
    023import java.util.Collections;
    024import java.util.Enumeration;
    025import java.util.HashMap;
    026import java.util.Iterator;
    027import java.util.List;
    028import java.util.Map;
    029import java.util.Properties;
    030import java.util.TreeSet;
    031
    032
    033/**
    034 * Simple utility that shows you a sorted list of property differences between
    035 * the 'default en' and a given i18n file. It also warns if there are any duplicates.
    036 * <p>
    037 * The first argument is the language, and it is mandatory.
    038 * The second argument is the path.  If the path is not defined, uses current path (.)
    039 * <p>
    040 * For example (if you're compiling your classes to "classes"):
    041 * <code>
    042 * java -cp classes TranslationsCheck fi
    043 * </code>
    044 */
    045public class TranslationsCheck {
    046
    047    private final static String[] LANGS = { "de", "en", "es", "fi", "fr", "it", "nl", "pt_BR", "ru", "zh_CN" };
    048    private final static String SITE_I18N_ROW =
    049        "<tr%s>\n" +
    050        "  <td title=\"Available sets of core WikiPages for %s\"><a class=\"external\" href=\"https://search.maven.org/artifact/org.apache.jspwiki.wikipages/jspwiki-wikipages-%s\">%s</a></td>\n" +
    051        "  <td>%d%%</td>\n" +
    052        "  <td>%d</td>\n" +
    053        "  <td>%d</td>\n" +
    054        "</tr>\n";
    055
    056    private final TreeSet< String > allProps = new TreeSet<>();  // sorted, no duplicates
    057    private final TreeSet< String > duplProps = new TreeSet<>();
    058
    059    // Change these to your settings...
    060    String base = ".";
    061    String suffix;
    062
    063    public static void main( final String[] args ) throws IOException {
    064        final TranslationsCheck translations = new TranslationsCheck();
    065        if( args.length == 0 ) {
    066            System.out.println( "Usage: java TranslationsCheck <language> [<path>]" );
    067            System.out.println( "Example: java TranslationsCheck nl [jspwiki-main/src/main/resources]" );
    068            System.out.println( "To output site i18n info use java TranslationsCheck site [<path>]" );
    069            return;
    070        }
    071        translations.suffix = args[ 0 ];
    072        if( args.length >= 2 ) {
    073            translations.base = args[ 1 ];
    074        }
    075
    076        if( "site".equals( translations.suffix ) ) {
    077            final StringBuilder site = new StringBuilder();
    078            for( int i = 0; i < LANGS.length; i++ ) {
    079                translations.suffix = LANGS[ i ];
    080                site.append(translations.check(i));
    081            }
    082            site.append("</table>\n" + // close table and formatting divs
    083                    "</div>\n" + "</div>\n" + "</div>");
    084            Files.write( Paths.get( "./i18n-table.txt" ), site.toString().getBytes( StandardCharsets.UTF_8 ) );
    085        } else {
    086            translations.check( -1 );
    087        }
    088    }
    089
    090    String check( final int lang ) throws IOException {
    091        // System.out.println( "Using code base " + Release.VERSTR );
    092        System.out.println( "Internationalization property file differences between 'default en' and '" + suffix + "' following:\n" );
    093
    094        final String fileSuffix = ( "en".equals( suffix ) ) ? "" : "_" + suffix;
    095        final Map< String, Integer > coreMetrics = checkFile( "/CoreResources.properties", "/CoreResources" + fileSuffix + ".properties" );
    096        final Map< String, Integer > templateMetrics = checkFile( "/templates/default.properties", "/templates/default" + fileSuffix + ".properties" );
    097        final Map< String, Integer > pluginMetrics = checkFile( "/plugin/PluginResources.properties", "/plugin/PluginResources" + fileSuffix + ".properties" );
    098
    099        if( lang >= 0 ) {
    100            final int expected = coreMetrics.get( "expected" ) + templateMetrics.get( "expected" ) + pluginMetrics.get( "expected" );
    101            final int missing = coreMetrics.get( "missing" ) + templateMetrics.get( "missing" ) + pluginMetrics.get( "missing" );
    102            final int completed = 100 * ( expected - missing ) / expected;
    103            final int outdated = coreMetrics.get( "outdated" ) + templateMetrics.get( "outdated" ) + pluginMetrics.get( "outdated" );
    104            final String odd = lang %2 == 0 ? " class=\"odd\"" : ""; // 0 first row
    105
    106            return String.format( SITE_I18N_ROW, odd, suffix, suffix, suffix, completed, missing, outdated );
    107        }
    108        return "";
    109    }
    110
    111    Map< String, Integer > checkFile( final String en, final String lang ) throws IOException {
    112        final Map< String, Integer > metrics = new HashMap<>();
    113        try {
    114            metrics.putAll( diff( en, lang ) );
    115            metrics.put( "duplicates", detectDuplicates( lang ) );
    116        } catch( final FileNotFoundException e ) {
    117            System.err.println( "Unable to locate " + lang );
    118        }
    119        System.out.println( "Duplicates overall (two or more occurences):" );
    120        System.out.println( "--------------------------------------------" );
    121        final Iterator< String > iter = duplProps.iterator();
    122        if(duplProps.isEmpty()) {
    123            System.out.println( "(none)" );
    124        } else {
    125            while( iter.hasNext() ) {
    126                System.out.println( iter.next() );
    127            }
    128        }
    129        System.out.println( );
    130        return metrics;
    131    }
    132
    133    public Map< String, Integer > diff( final String source1, final String source2 ) throws IOException {
    134        int missing = 0, outdated = 0;
    135        // Standard Properties
    136        final Properties p1 = new Properties();
    137        p1.load( getResourceAsStream( source1 ) );
    138
    139        final Properties p2 = new Properties();
    140        p2.load( getResourceAsStream( source2 ) );
    141
    142        final String msg = "Checking " + source2 + "...";
    143        System.out.println( msg );
    144
    145        Iterator< String > iter = sortedNames( p1 ).iterator();
    146        while( iter.hasNext() ) {
    147            final String name = iter.next();
    148            final String value = p1.getProperty( name );
    149
    150            if( p2.get( name ) == null ) {
    151                missing++;
    152                if( missing == 1 ) {
    153                    System.out.println( "\nMissing:" );
    154                    System.out.println( "--------" );
    155                }
    156                System.out.println( name + " = " + value );
    157            }
    158        }
    159        if( missing > 0 ) {
    160            System.out.println();
    161        }
    162
    163        iter = sortedNames( p2 ).iterator();
    164        while( iter.hasNext() ) {
    165            final String name = iter.next();
    166            final String value = p2.getProperty( name );
    167
    168            if( p1.get( name ) == null ) {
    169                outdated++;
    170                if( outdated == 1 ) {
    171                    System.out.println( "\nOutdated or superfluous:" );
    172                    System.out.println( "------------------------" );
    173                }
    174                System.out.println( name + " = " + value );
    175            }
    176        }
    177        if( outdated > 0 ) {
    178            System.out.println();
    179        }
    180
    181        final Map< String, Integer > diff = new HashMap<>( 2 );
    182        diff.put( "expected", p1.size() );
    183        diff.put( "missing", missing );
    184        diff.put( "outdated", outdated );
    185        return diff;
    186    }
    187
    188    private List< String > sortedNames( final Properties p ) {
    189        final List< String > list = new ArrayList<>();
    190        final Enumeration< ? > iter = p.propertyNames();
    191        while( iter.hasMoreElements() ) {
    192            list.add( ( String )iter.nextElement() );
    193        }
    194
    195        Collections.sort( list );
    196        return list;
    197    }
    198
    199    public int detectDuplicates( final String source ) throws IOException {
    200        final Properties p = new Properties();
    201        p.load( getResourceAsStream( source ) );
    202        final Enumeration< ? > iter = p.propertyNames();
    203        String currentStr;
    204        while( iter.hasMoreElements() ) {
    205            currentStr = ( String )iter.nextElement();
    206            if( !allProps.add( currentStr ) ) {
    207                duplProps.add( currentStr );
    208            }
    209        }
    210        return duplProps.size();
    211    }
    212
    213    InputStream getResourceAsStream( final String source ) {
    214        return TranslationsCheck.class.getClassLoader().getResourceAsStream( base + source );
    215    }
    216
    217}