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 = null;
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            String site = "";
078            for( int i = 0; i < LANGS.length; i++ ) {
079                translations.suffix = LANGS[ i ];
080                site += translations.check( i );
081            }
082            site += "</table>\n" + // close table and formatting divs
083                    "</div>\n" +
084                    "</div>\n" +
085                    "</div>";
086            Files.write( Paths.get( "./i18n-table.txt" ), site.getBytes( StandardCharsets.UTF_8 ) );
087        } else {
088            translations.check( -1 );
089        }
090    }
091
092    String check( final int lang ) throws IOException {
093        // System.out.println( "Using code base " + Release.VERSTR );
094        System.out.println( "Internationalization property file differences between 'default en' and '" + suffix + "' following:\n" );
095
096        final String fileSuffix = ( "en".equals( suffix ) ) ? "" : "_" + suffix;
097        final Map< String, Integer > coreMetrics = checkFile( "/CoreResources.properties", "/CoreResources" + fileSuffix + ".properties" );
098        final Map< String, Integer > templateMetrics = checkFile( "/templates/default.properties", "/templates/default" + fileSuffix + ".properties" );
099        final Map< String, Integer > pluginMetrics = checkFile( "/plugin/PluginResources.properties", "/plugin/PluginResources" + fileSuffix + ".properties" );
100
101        if( lang >= 0 ) {
102            final int expected = coreMetrics.get( "expected" ) + templateMetrics.get( "expected" ) + pluginMetrics.get( "expected" );
103            final int missing = coreMetrics.get( "missing" ) + templateMetrics.get( "missing" ) + pluginMetrics.get( "missing" );
104            final int completed = 100 * ( expected - missing ) / expected;
105            final int outdated = coreMetrics.get( "outdated" ) + templateMetrics.get( "outdated" ) + pluginMetrics.get( "outdated" );
106            final String odd = lang %2 == 0 ? " class=\"odd\"" : ""; // 0 first row
107
108            return String.format( SITE_I18N_ROW, odd, suffix, suffix, suffix, completed, missing, outdated );
109        }
110        return "";
111    }
112
113    Map< String, Integer > checkFile( final String en, final String lang ) throws IOException {
114        final Map< String, Integer > metrics = new HashMap<>();
115        try {
116            metrics.putAll( diff( en, lang ) );
117            metrics.put( "duplicates", detectDuplicates( lang ) );
118        } catch( final FileNotFoundException e ) {
119            System.err.println( "Unable to locate " + lang );
120        }
121        System.out.println( "Duplicates overall (two or more occurences):" );
122        System.out.println( "--------------------------------------------" );
123        final Iterator< String > iter = duplProps.iterator();
124        if( duplProps.size() == 0 ) {
125            System.out.println( "(none)" );
126        } else {
127            while( iter.hasNext() ) {
128                System.out.println( iter.next() );
129            }
130        }
131        System.out.println( "" );
132        return metrics;
133    }
134
135    public Map< String, Integer > diff( final String source1, final String source2 ) throws IOException {
136        int missing = 0, outdated = 0;
137        // Standard Properties
138        final Properties p1 = new Properties();
139        p1.load( getResourceAsStream( source1 ) );
140
141        final Properties p2 = new Properties();
142        p2.load( getResourceAsStream( source2 ) );
143
144        final String msg = "Checking " + source2 + "...";
145        System.out.println( msg );
146
147        Iterator< String > iter = sortedNames( p1 ).iterator();
148        while( iter.hasNext() ) {
149            final String name = iter.next();
150            final String value = p1.getProperty( name );
151
152            if( p2.get( name ) == null ) {
153                missing++;
154                if( missing == 1 ) {
155                    System.out.println( "\nMissing:" );
156                    System.out.println( "--------" );
157                }
158                System.out.println( name + " = " + value );
159            }
160        }
161        if( missing > 0 ) {
162            System.out.println( "" );
163        }
164
165        iter = sortedNames( p2 ).iterator();
166        while( iter.hasNext() ) {
167            final String name = iter.next();
168            final String value = p2.getProperty( name );
169
170            if( p1.get( name ) == null ) {
171                outdated++;
172                if( outdated == 1 ) {
173                    System.out.println( "\nOutdated or superfluous:" );
174                    System.out.println( "------------------------" );
175                }
176                System.out.println( name + " = " + value );
177            }
178        }
179        if( outdated > 0 ) {
180            System.out.println( "" );
181        }
182
183        final Map< String, Integer > diff = new HashMap<>( 2 );
184        diff.put( "expected", p1.size() );
185        diff.put( "missing", missing );
186        diff.put( "outdated", outdated );
187        return diff;
188    }
189
190    private List< String > sortedNames( final Properties p ) {
191        final List< String > list = new ArrayList<>();
192        final Enumeration< ? > iter = p.propertyNames();
193        while( iter.hasMoreElements() ) {
194            list.add( ( String )iter.nextElement() );
195        }
196
197        Collections.sort( list );
198        return list;
199    }
200
201    public int detectDuplicates( final String source ) throws IOException {
202        final Properties p = new Properties();
203        p.load( getResourceAsStream( source ) );
204        final Enumeration< ? > iter = p.propertyNames();
205        String currentStr;
206        while( iter.hasMoreElements() ) {
207            currentStr = ( String )iter.nextElement();
208            if( !allProps.add( currentStr ) ) {
209                duplProps.add( currentStr );
210            }
211        }
212        return duplProps.size();
213    }
214
215    InputStream getResourceAsStream( final String source ) {
216        return TranslationsCheck.class.getClassLoader().getResourceAsStream( base + source );
217    }
218
219}