1 /*******************************************************************************
2  * Copyright (c) 2010 - 2013 by Timotei Dolean <timotei21@gmail.com>
3  *
4  * This program and the accompanying materials are made available
5  * under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *******************************************************************************/
9 package org.wesnoth.preprocessor;
10 
11 import java.io.File;
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 
20 import org.eclipse.core.filesystem.EFS;
21 import org.eclipse.core.filesystem.IFileStore;
22 import org.eclipse.core.resources.IFile;
23 import org.eclipse.core.resources.IResource;
24 import org.eclipse.core.runtime.Path;
25 import org.eclipse.jface.dialogs.DialogSettings;
26 
27 import org.wesnoth.Logger;
28 import org.wesnoth.Messages;
29 import org.wesnoth.preferences.Preferences;
30 import org.wesnoth.preferences.Preferences.Paths;
31 import org.wesnoth.projects.ProjectUtils;
32 import org.wesnoth.utils.EditorUtils;
33 import org.wesnoth.utils.ExternalToolInvoker;
34 import org.wesnoth.utils.ResourceUtils;
35 import org.wesnoth.utils.WorkspaceUtils;
36 
37 /**
38  * Utilities class for handling with the preprocessor
39  */
40 public class PreprocessorUtils
41 {
42     private static class PreprocessorUtilsInstance
43     {
44         private static PreprocessorUtils instance_ = new PreprocessorUtils( );
45     }
46 
47     private Map< String, Long > filesTimeStamps_       = new HashMap< String, Long >( );
48 
49     private static final String PREPROCESSED_FILE_PATH = WorkspaceUtils
50                                                            .getTemporaryFolder( )
51                                                            + "preprocessed.txt";
52 
PreprocessorUtils( )53     private PreprocessorUtils( )
54     {
55         filesTimeStamps_ = new HashMap< String, Long >( );
56         restoreTimestamps( );
57     }
58 
59     /**
60      * Returns the singleton instance.
61      *
62      * @return The {@link PreprocessorUtils} singleton instance.
63      */
getInstance( )64     public static PreprocessorUtils getInstance( )
65     {
66         return PreprocessorUtilsInstance.instance_;
67     }
68 
69     /**
70      * preprocesses a file using the wesnoth's executable, only
71      * if the file was modified since last time checked.
72      * The target directory is the temporary directory + files's path relative
73      * to project
74      *
75      * @param file
76      *        the file to process
77      * @param defines
78      *        the list of additional defines to be added when preprocessing
79      *        the file
80      * @return
81      *         -1 - we skipped preprocessing - file was already preprocessed
82      *         0 - preprocessed succesfully
83      *         1 - there was an error
84      */
preprocessFile( IFile file, List< String > defines )85     public int preprocessFile( IFile file, List< String > defines )
86     {
87         return preprocessFile( file, getPreprocessedFileLocation( file ),
88             getMacrosLocation( file ), defines, true );
89     }
90 
91     /**
92      * preprocesses a file using the wesnoth's executable, only
93      * if the file was modified since last time checked.
94      * The target directory is the temporary directory + files's path relative
95      * to project
96      *
97      * @param file
98      *        the file to process
99      * @param macrosFile
100      *        The file where macros are stored
101      * @param defines
102      *        the list of additional defines to be added when preprocessing
103      *        the file
104      * @return
105      *         -1 - we skipped preprocessing - file was already preprocessed
106      *         0 - preprocessed succesfully
107      *         1 - there was an error
108      */
preprocessFile( IFile file, String macrosFile, List< String > defines )109     public int preprocessFile( IFile file, String macrosFile,
110         List< String > defines )
111     {
112         return preprocessFile( file, getPreprocessedFileLocation( file ),
113             macrosFile, defines, true );
114     }
115 
116     /**
117      * preprocesses a file using the wesnoth's executable, only
118      * if the file was modified since last time checked.
119      *
120      * @param file
121      *        the file to process
122      * @param targetDirectory
123      *        target directory where should be put the results
124      * @param macrosFile
125      *        The file where macros are stored
126      * @param defines
127      *        the list of additional defines to be added when preprocessing
128      *        the file
129      * @param waitForIt
130      *        true to wait for the preprocessing to finish
131      * @return
132      *         -1 - we skipped preprocessing - file was already preprocessed
133      *         0 - preprocessed succesfully
134      *         1 - there was an error
135      */
preprocessFile( IFile file, String targetDirectory, String macrosFile, List< String > defines, boolean waitForIt )136     public int preprocessFile( IFile file, String targetDirectory,
137         String macrosFile, List< String > defines, boolean waitForIt )
138     {
139         String filePath = file.getLocation( ).toOSString( );
140         if( filesTimeStamps_.containsKey( filePath )
141             && filesTimeStamps_.get( filePath ) >= new File( filePath )
142                 .lastModified( ) ) {
143             Logger.getInstance( ).logTool(
144                 "skipped preprocessing a non-modified file: " + filePath ); //$NON-NLS-1$
145             return - 1;
146         }
147 
148         filesTimeStamps_.put( filePath, new File( filePath ).lastModified( ) );
149 
150         Paths paths = Preferences
151             .getPaths( ProjectUtils.getCacheForProject(
152                 file.getProject( ) ).getInstallName( ) );
153 
154         List< String > arguments = new ArrayList< String >( );
155 
156         arguments.add( "--config-dir" ); //$NON-NLS-1$
157         arguments.add( paths.getUserDir( ) );
158 
159         arguments.add( "--data-dir" ); //$NON-NLS-1$
160         arguments.add( paths.getWorkingDir( ) );
161 
162         if( macrosFile != null && macrosFile.isEmpty( ) == false ) {
163             ResourceUtils.createNewFile( macrosFile );
164 
165             // add the _MACROS_.cfg file
166             arguments.add( "--preprocess-input-macros" ); //$NON-NLS-1$
167             arguments.add( macrosFile );
168 
169 
170             arguments.add( "--preprocess-output-macros" ); //$NON-NLS-1$
171             arguments.add( macrosFile );
172         }
173 
174         if( Preferences.getBool( Preferences.NO_TERRAIN_GFX ) ) {
175             if( defines == null ) {
176                 defines = new ArrayList< String >( );
177             }
178             defines.add( "NO_TERRAIN_GFX" ); //$NON-NLS-1$
179         }
180 
181         // --preprocess
182         arguments.add( "-p" ); //$NON-NLS-1$
183         arguments.add( filePath );
184         arguments.add( targetDirectory );
185 
186         // --preprocess-defines
187         if( defines != null && ! defines.isEmpty( ) ) {
188             arguments.add( "--preprocess-defines" ); //$NON-NLS-1$
189 
190             StringBuilder definesArg = new StringBuilder( );
191             for( Iterator< String > itor = defines.iterator( ); itor
192                 .hasNext( ); ) {
193                 if( definesArg.length( ) > 0 ) {
194                     definesArg.append( "," ); //$NON-NLS-1$
195                 }
196 
197                 definesArg.append( itor.next( ) );
198             }
199 
200             arguments.add( definesArg.toString( ) );
201         }
202 
203         Logger.getInstance( ).logTool( "preprocessing file: " + filePath ); //$NON-NLS-1$
204         ExternalToolInvoker wesnoth = new ExternalToolInvoker(
205             paths.getWesnothExecutablePath( ), arguments );
206         wesnoth.runTool( );
207         if( waitForIt ) {
208             return wesnoth.waitForTool( );
209         }
210         return 0;
211     }
212 
213     /**
214      * Opens the preprocessed version of the specified file
215      *
216      * @param file
217      *        the file to show preprocessed output
218      * @param openPlain
219      *        true if it should open the plain preprocessed version
220      *        or false for the normal one
221      */
openPreprocessedFileInEditor( IFile file, boolean openPlain )222     public void openPreprocessedFileInEditor( IFile file, boolean openPlain )
223     {
224         if( file == null || ! file.exists( ) ) {
225             Logger.getInstance( ).log( "file null or non existent.", //$NON-NLS-1$
226                 Messages.PreprocessorUtils_12 );
227             return;
228         }
229         EditorUtils
230             .openEditor( getPreprocessedFilePath( file, openPlain, true ) );
231     }
232 
233     /**
234      * Returns the path of the preprocessed file of the specified file
235      *
236      * @param file
237      *        The file whom preprocessed file to get
238      * @param plain
239      *        True to return the plain version file's file
240      * @param create
241      *        if this is true, if the target preprocessed file
242      *        doesn't exist it will be created.
243      * @return The {@link IFileStore} which contains the preprocessed file
244      */
getPreprocessedFilePath( IFile file, boolean plain, boolean create )245     public IFileStore getPreprocessedFilePath( IFile file, boolean plain,
246         boolean create )
247     {
248         IFileStore preprocFile = EFS.getLocalFileSystem( ).getStore(
249             new Path( getPreprocessedFileLocation( file ) ) );
250         preprocFile = preprocFile.getChild( file.getName( )
251             + ( plain == true ? ".plain": "" ) ); //$NON-NLS-1$ //$NON-NLS-2$
252         if( create && ! preprocFile.fetchInfo( ).exists( ) ) {
253             preprocessFile( file, null );
254         }
255         return preprocFile;
256     }
257 
258     /**
259      * Gets the temporary location where that file should be preprocessed
260      *
261      * @param file
262      *        The file to get the location for.
263      * @return A string representing the temporary location.
264      */
getPreprocessedFileLocation( IFile file )265     public String getPreprocessedFileLocation( IFile file )
266     {
267         String targetDirectory = WorkspaceUtils.getTemporaryFolder( );
268         targetDirectory += file.getProject( ).getName( ) + "/"; //$NON-NLS-1$
269         targetDirectory += file.getParent( ).getProjectRelativePath( )
270             .toOSString( )
271             + "/"; //$NON-NLS-1$
272         return targetDirectory;
273     }
274 
275     /**
276      * Gets the location where the '_MACROS_.cfg' file is for the
277      * specified resource.
278      *
279      * Currently we store just a defines file per project.
280      *
281      * @param resource
282      *        The resource to get the location for
283      * @return A string that points to the macros file.
284      */
getMacrosLocation( IResource resource )285     public String getMacrosLocation( IResource resource )
286     {
287         return WorkspaceUtils
288             .getProjectTemporaryFolder( resource.getProject( ) )
289             + "/_MACROS_.cfg"; //$NON-NLS-1$
290     }
291 
292     /**
293      * Saves the current timestamps for preprocessed files
294      * to filesystem
295      */
saveTimestamps( )296     public void saveTimestamps( )
297     {
298         DialogSettings settings = new DialogSettings( "preprocessed" ); //$NON-NLS-1$
299         try {
300             settings
301                 .put( "files", //$NON-NLS-1$
302                     filesTimeStamps_.keySet( ).toArray(
303                         new String[filesTimeStamps_.size( )] ) );
304             List< String > timestamps = new ArrayList< String >( );
305             for( Long timestamp: filesTimeStamps_.values( ) ) {
306                 timestamps.add( timestamp.toString( ) );
307             }
308             settings
309                 .put( "timestamps", //$NON-NLS-1$
310                     timestamps.toArray( new String[timestamps.size( )] ) );
311             settings.save( PREPROCESSED_FILE_PATH );
312         } catch( Exception e ) {
313             Logger.getInstance( ).logException( e );
314         }
315     }
316 
317     /**
318      * Restores the timestamps for preprocessed files from
319      * the filesystem
320      */
restoreTimestamps( )321     public void restoreTimestamps( )
322     {
323         DialogSettings settings = new DialogSettings( "preprocessed" ); //$NON-NLS-1$
324         filesTimeStamps_.clear( );
325 
326         try {
327             // ensure the creation of a valid file if it doesn't exist
328             if( ! new File( PREPROCESSED_FILE_PATH ).exists( ) ) {
329                 settings.save( PREPROCESSED_FILE_PATH );
330             }
331 
332             settings.load( PREPROCESSED_FILE_PATH );
333             String[] timestamps = settings.getArray( "timestamps" ); //$NON-NLS-1$
334             String[] files = settings.getArray( "files" ); //$NON-NLS-1$
335             if( timestamps != null && files != null
336                 && timestamps.length == files.length ) {
337                 for( int index = 0; index < files.length; ++index ) {
338                     filesTimeStamps_.put( files[index],
339                         Long.valueOf( timestamps[index] ) );
340                 }
341             }
342         } catch( IOException e ) {
343             Logger.getInstance( ).logException( e );
344         }
345     }
346 
347     /**
348      * Clears all timestamps cached for files that are located in that path
349      *
350      * @param path
351      *        The path to match the files to clear their timestamp
352      */
clearTimestampsForPath( String path )353     public void clearTimestampsForPath( String path )
354     {
355         Iterator< Entry< String, Long >> itor = filesTimeStamps_.entrySet( )
356             .iterator( );
357 
358         while( itor.hasNext( ) ) {
359             Entry< String, Long > entry = itor.next( );
360             if( entry.getKey( ).startsWith( path ) ) {
361                 itor.remove( );
362             }
363         }
364 
365         saveTimestamps( );
366     }
367 }
368