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.size() == 0 ) {
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}