/*
* Name: Service
* Authors: Richard Rodger
*
* Copyright (c) 2000-2006 Richard Rodger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
// package
package org.jostraca;
// import
import org.jostraca.process.ProcessManager;
import org.jostraca.process.BasicProcessManager;
import org.jostraca.process.MetaUtil;
import org.jostraca.process.ProcessException;
import org.jostraca.process.GenericPreparer;
import org.jostraca.util.Internal;
import org.jostraca.util.ErrorUtil;
import org.jostraca.util.Tracker;
import org.jostraca.util.Standard;
import org.jostraca.util.ListUtil;
import org.jostraca.util.ArgUtil;
import org.jostraca.util.BasicWayPoint;
import org.jostraca.util.WayPointRecorder;
import org.jostraca.util.StandardException;
import org.jostraca.util.RootBuildResource;
import org.jostraca.util.FileBuildResource;
import org.jostraca.util.RegExp;
import org.jostraca.util.RegExpProvider;
import org.jostraca.util.PropertySet;
import org.jostraca.util.PropertySetManager;
import org.jostraca.util.OrderedPropertySetManager;
import org.jostraca.util.PropertySetModifierManager;
import org.jostraca.util.UserMessageHandler;
import org.jostraca.util.CommandLineUserMessageHandler;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
/** The <code>Service</code> class provides the top level code generation functionality of Jostraca.
* <p>This class is used by other classes which provide a code generation service and therefore
* provides the full functionality of Jostraca in a relatively complex API.</p>
* <p>To use Jostraca as a component, try the {@link org.jostraca.Generator} class, which is easier to use than <code>Service</code>.</p>
*/
public class Service implements Constants {
// public static
public static final String CN = Service.class.getName();
public static final String STANDARD_TRACKER = "s";
public static final Tracker t = Tracker.getTracker( STANDARD_TRACKER );
public static final String DUMP_SPEC_settings = "settings";
public static final String DUMP_SPEC_template = "template";
// private instance
// list of templates to build
private List iTemplatePaths = new ArrayList();
// template object factory - create objects which describe template properties
private TemplateFactory iTemplateFactory = new BasicTemplateFactory();
// manage property set precedences
private OrderedPropertySetManager iContextPSM = null;
// manage language PropertySets
private PropertySetManager iLangPS = null;
// manage property set modifiers
private PropertySetModifierManager iPropertySetModifierManager = new PropertySetModifierManager();
// default for command line use
private UserMessageHandler iUserMessageHandler = new CommandLineUserMessageHandler();
// configuration files folder
private String iConfigFolder = Standard.EMPTY;
// context object passed to internal code writer
private Object iContextForTemplate = null;
// public methods
/** Create Service object to run code generation. */
public Service() {
iContextPSM = DefaultPropertySets.makeStandardOrder();
iContextPSM.put( Service.CONF_system, DefaultPropertySets.makeSystemPropertySet() );
iLangPS = DefaultPropertySets.makeLang();
setRegExpProvider( iContextPSM.get( Service.CONF_system ) );
}
/*
public static void activateTracking() {
}
*/
/** Set user message handler.
* @param pUserMessageHandler UserMessageHandler instance
*/
public void setUserMessageHandler( UserMessageHandler pUserMessageHandler ) {
iUserMessageHandler = (UserMessageHandler) Internal.null_arg( pUserMessageHandler );
}
/** Set template paths. {@link org.jostraca.TemplatePath} objects
* encapsulate the concept of a template path, where the path can
* be a file path (foo/bar/a.jtm) or an abstract template library
* path (foo.bar.a).
* @param pTemplatePaths Paths to template files
*/
public void setTemplatePaths( List pTemplatePaths ) {
iTemplatePaths = (List) Internal.null_arg( pTemplatePaths );
}
/** Set configuration files folder.
* @param pConfigFolder Path to config files
*/
public void setConfigFolder( String pConfigFolder ) {
iConfigFolder = ( String ) Internal.null_arg( pConfigFolder );
}
/** Set context for template to use.
* @param pContextForTemplate context object
*/
public void setContextForTemplate( Object pContextForTemplate ) {
iContextForTemplate = ( Object ) Internal.null_arg( pContextForTemplate );
}
/** Add a PropertySet by name. Unknown names are ignored.
* @param pName Name of PropertySet
* @param pPropertySet PropertySet to add
*/
public void addPropertySet( String pName, PropertySet pPropertySet ) {
PropertySet propSet = pPropertySet;
if( ErrorUtil.not_null( pName ) ) {
if( ErrorUtil.is_null( propSet ) ) {
propSet = new PropertySet(); // add an empty one
}
iContextPSM.put( pName, propSet );
t.track( ".addPropertySet:", pName+" size:"+propSet.size() );
if( CONF_system.equals( pName ) ) {
setRegExpProvider( propSet );
}
}
}
public PropertySet getCurrentConfig() {
return iContextPSM.merge();
}
/** Loads system configuration from specified folder.
*
*/
public void loadConfigFromFolder( File pConfigFolder, ArrayList pAdditionalConfig ) {
File configFolder = (File) Internal.null_arg( pConfigFolder );
t.track( ".loadConfigFromFolder:", configFolder );
PropertySet systemPS = null;
setConfigFolder( configFolder.getAbsolutePath() );
// REVIEW: need a better way to do this!
File systemConfFile = new File( iConfigFolder, FILE_NAME_system_conf );
systemPS = loadBaseConfigFiles( systemConfFile );
PropertySet addps = loadAdditionalConfigFiles( pAdditionalConfig );
systemPS.overrideWith( addps );
PropertySet local = new PropertySet();
File localConfigFile = new File( pConfigFolder, systemPS.get( Property.jostraca_LocalConfigFileName ) );
if( localConfigFile.exists() && !localConfigFile.isDirectory() ) {
local.load( localConfigFile );
systemPS.overrideWith( local );
}
else {
// if local.conf does not exist, ignore
}
Tools.produceJostracaLocation( systemPS, systemConfFile );
addPropertySet( CONF_system, systemPS );
}
/** Load Additional config files and apply to system config.
*/
private PropertySet loadAdditionalConfigFiles( ArrayList pAdditionalConfigFiles ) {
PropertySet result = new PropertySet();
int numAddConf = pAdditionalConfigFiles.size();
for(int addConfI = 0; addConfI < numAddConf; addConfI++) {
String path = (String) pAdditionalConfigFiles.get( addConfI );
PropertySet addConf = new PropertySet();
addConf.load( new File(path) );
result.overrideWith( addConf );
}
return result;
}
/** Build code from prespecified list of TemplatePath objects.
* @return List of Template objects
*/
public List build() {
List templates = build( iTemplatePaths );
return templates;
}
/** Build list of {@link org.jostraca.Template} or {@link org.jostraca.TemplatePath} objects.
* @return List of Template objects
*/
public List build( List pTemplateObjects ) {
List tmlist = new ArrayList();
List tmolist = (List) Internal.null_arg( pTemplateObjects );
if( 0 < tmolist.size() ) {
if( tmolist.get(0) instanceof TemplatePath ) {
tmlist = buildTemplatePaths( tmolist );
}
else {
tmlist = buildTemplates( tmolist );
}
}
return tmlist;
}
/** Build template from template path.
* @return List of Template objects
*/
public List build( TemplatePath pTemplatePath ) {
TemplatePath tmpath = (TemplatePath) Internal.null_arg( pTemplatePath );
List tmpathlist = ListUtil.make( tmpath );
List tmlist = buildTemplatePaths( tmpathlist );
return tmlist;
}
/** Build templates from list of template paths.
* @return List of Template objects
*/
public List buildTemplatePaths( List pTemplatePaths ) {
List templatepaths = (List) Internal.null_arg( pTemplatePaths );
t.track( "buildTemplatePaths:", ""+templatepaths );
List tmlistin = new ArrayList();
int numtmp = templatepaths.size();
for( int tmpI = 0; tmpI < numtmp; tmpI++ ) {
TemplatePath tmpath = (TemplatePath) templatepaths.get(tmpI);
if( null == tmpath ) {
throw ServiceException.CODE_null_tmpath( "index", String.valueOf(tmpI) );
}
else {
Template tm = makeTemplate( tmpath );
tmlistin.add( tm );
}
}
List tmlist = buildTemplates( tmlistin );
return tmlist;
}
/** Build a template.
* @return Template object
*/
public Template build( Template pTemplate ) {
Template template = (Template) Internal.null_arg( pTemplate );
List tmlistin = ListUtil.make( template );
List tmlist = buildTemplates( tmlistin );
if( 0 < tmlist.size() ) {
return (Template) tmlist.get(0);
}
else {
return pTemplate;
}
}
/** Build a list of templates.
* @return List of Template objects
*/
public List buildTemplates( List pTemplates ) {
List templates = (List) Internal.null_arg( pTemplates );
t.track( "buildTemplates:", ""+templates );
ErrorUtil.not_null( iUserMessageHandler );
PropertySet contextps = iContextPSM.merge();
// process templates
ArrayList tmlist = new ArrayList();
for( Iterator tmT = pTemplates.iterator(); tmT.hasNext(); ) {
Template tm = (Template) tmT.next();
// set config - user may have created Template object directly
List psnL = iContextPSM.listNames();
for( Iterator psnT = psnL.iterator(); psnT.hasNext(); ) {
String psn = (String) psnT.next();
PropertySet tmps = tm.getPropertySet(psn);
tmps.inheritFrom( iContextPSM.get( psn ) );
tm.setPropertySet( psn, tmps );
}
// HACK: as we need some property mods before checkUpToDate
GenericPreparer.handleCodeWriterName( tm );
// drop templates that are up-to-date
boolean uptodate = checkUpToDate( tm );
if( uptodate ) {
WayPointRecorder.add( BasicWayPoint.GenerationUptodate.make( tm.getTemplatePath().getTemplatePath() ) );
iUserMessageHandler.info( "Uptodate:", tm.getTemplatePath().getTemplateName() );
}
else {
tmlist.add( tm );
}
}
t.track( "buildTemplates attempt:", ""+tmlist );
ProcessManager pm = makeProcessManager( contextps );
pm.setUserMessageHandler( iUserMessageHandler );
pm.process( tmlist );
// return full list, including those not generated
return templates;
}
/** Create a template object from a template path */
public Template makeTemplate( TemplatePath pTemplatePath ) {
TemplatePath tmpath = (TemplatePath) Internal.null_arg( pTemplatePath );
t.track( "TemplatePath", pTemplatePath );
PropertySet contextps = iContextPSM.merge();
t.track( "build.TemplatePath.contextps.size", contextps.size() );
// FIX: consider sending ps to tmpath instead to hide this
String[] tmsearchpath = contextps.getList( Property.main_TemplatePath, Standard.COMMA );
tmpath.resolve( tmsearchpath );
// REVIEW: l10n
iUserMessageHandler.info( "Template:", tmpath.getTemplateName() );
iUserMessageHandler.debug( "Template Path:", tmpath.getTemplatePath() );
Template template = iTemplateFactory.createTemplate( pTemplatePath, iContextPSM );
if( null != iContextForTemplate ) {
template.setContext( iContextForTemplate );
}
return template;
}
// REVIEW: Jostraca should use this
/** Load system and local configuration */
public PropertySet loadBaseConfigFiles( File pSystemConfigFile ) {
File systemConfigFile = (File) Internal.null_arg( pSystemConfigFile );
boolean systemConfigExists = systemConfigFile.exists();
iContextPSM.load( CONF_system, systemConfigFile, PropertySetManager.USE_DEFAULT_IF_FILE_DOES_NOT_EXIST );
PropertySet system = iContextPSM.get( CONF_system );
File configFolder = systemConfigFile.getParentFile();
if( null == configFolder ) {
configFolder = new File( "" );
}
PropertySet local = new PropertySet();
File localConfigFile = new File( configFolder, system.get( Property.jostraca_LocalConfigFileName ) );
if( localConfigFile.exists() && !localConfigFile.isDirectory() ) {
local.load( localConfigFile );
}
system.overrideWith( local );
// only set jostraca.Location if using configuration from disk
if( systemConfigExists ) {
Tools.produceJostracaLocation( system, systemConfigFile );
}
return system;
}
/** return a List of TemplatPath objects */
public static List buildTemplateListFromFile( File pFile ) {
File tlf = (File) Internal.null_arg( pFile );
try {
ArrayList templates = new ArrayList();
FileReader fr = new FileReader( pFile );
BufferedReader br = new BufferedReader( fr );
String line = null;
while( null != ( line = br.readLine() ) ) {
line = line.trim();
if( 0 == line.length() ) {
continue;
}
BasicTemplatePath btp = new BasicTemplatePath( line, tlf.getAbsolutePath() );
templates.add( btp );
}
fr.close();
return templates;
}
catch( StandardException se ) {
throw se;
}
catch( Exception e ) {
throw new TemplateException( TemplateException.CODE_load_file, pFile );
}
}
// FIX: funny place for this - surely Jostraca is better
/** Set dump options from dump specification argument.
* expected format: propertyset,template,... etc
* @param pDumpSpec dump specification String
* @param pCmdLineOptions dump settings placed here
*/
public static void parseDumpSpec( String pDumpSpec, PropertySet pCmdLineOptions ) {
if( -1 != pDumpSpec.indexOf( DUMP_SPEC_settings ) ) {
pCmdLineOptions.set( Property.main_DumpPropertySet, Standard.YES );
}
if( -1 != pDumpSpec.indexOf( DUMP_SPEC_template ) ) {
pCmdLineOptions.set( Property.main_DumpTemplate, Standard.YES );
}
}
/** Activate tracking of internal processing to a log file. */
public static void activateTracking( File pTrackFile ) {
File trackF = (File) Internal.null_arg( pTrackFile );
try {
File trackParentF = trackF.getParentFile();
if( !trackParentF.exists() ) {
trackParentF.mkdirs();
}
Tracker.activate( pTrackFile );
}
catch( Exception e ) {
throw new ServiceException( ServiceException.CODE_bad_track_file, trackF );
}
}
/** Deactivate tracking of internal processing. */
public static void deactivateTracking() {
Tracker.deactivate();
}
// private methods
private ProcessManager makeProcessManager( PropertySet pPropertySet ) {
ProcessManager pm = new BasicProcessManager( pPropertySet );
return pm;
}
private List prepare( List pTemplatePaths ) {
List templatePaths = (List) Internal.null_arg( pTemplatePaths );
ArrayList templates = new ArrayList();
PropertySet contextps = iContextPSM.merge();
for( Iterator tpT = templatePaths.iterator(); tpT.hasNext(); ) {
TemplatePath tp = (TemplatePath) tpT.next();
tp.resolve( contextps.getList( Property.main_TemplatePath, Standard.COMMA ) );
// REVIEW: l10n
iUserMessageHandler.info( "Template:", tp.getTemplateName() );
iUserMessageHandler.debug( "Template Path:", tp.getTemplatePath() );
Template template = iTemplateFactory.createTemplate( tp, iContextPSM );
if( null != iContextForTemplate ) {
template.setContext( iContextForTemplate );
}
templates.add( template );
}
return templates;
}
/** Check uptodate status. Try to determine generated files
* using build metadata, if available, and test last modified data
* against any CodeWriter options that are files, if found.
* Return true if uptodate.
*/
private boolean checkUpToDate( Template pTemplate ) {
PropertySet tmps = pTemplate.getMergedPropertySet();
// assume not uptodate
boolean uptodate = false;
try {
TemplatePath tp = pTemplate.getTemplatePath();
// some properties force regenerate
if( tmps.isYes( Property.main_CompileCodeWriter )
|| tmps.isYes( Property.main_ExecuteCodeWriter )
|| tmps.isNo( Property.main_EnableMeta ) ) {
uptodate = false;
iUserMessageHandler.debug( "Not uptodate:",
Property.main_CompileCodeWriter+":"+tmps.get( Property.main_CompileCodeWriter )
+ " " + Property.main_ExecuteCodeWriter+":"+tmps.get( Property.main_ExecuteCodeWriter )
+ " " + Property.main_EnableMeta+":"+tmps.get( Property.main_EnableMeta ) );
}
// check meta data
else {
PropertySet meta = null;
boolean proceed = true;
// load meta data
if( proceed ) {
try {
meta = MetaUtil.loadMetaData( pTemplate );
}
catch( ProcessException te ) {
if( ProcessException.CODE_load_meta == te.getCode() ) {
// meta data could not be loaded - not an error
}
else {
ErrorUtil.nonFatalMsg( te.getMessage() );
}
uptodate = false;
iUserMessageHandler.debug( "Not uptodate:", "unable to read meta data from previous generate." );
proceed = false;
}
}
// if template properties are different, regenerate
if( proceed ) {
// FIX: props from template only
String ctmps = Tools.normaliseTemplatePropertySet( pTemplate.getPropertySet( Constants.CONF_template ) );
String mtmps = meta.get( Property.jostraca_template_properties );
t.track( "current template ps", ctmps );
t.track( "prev template ps", mtmps );
if( !ctmps.equals( mtmps ) ) {
iUserMessageHandler.debug( "Not uptodate:", "template properties were different from previous generate." );
uptodate = false;
proceed = false;
//System.out.println( "ctmps:["+ctmps+"]" );
//System.out.println( "mtmps:["+mtmps+"]" );
}
}
// if cmd line is different, regenerate
if( proceed ) {
String co = normaliseCmdLine( tmps.get( Property.main_CodeWriterOptions ) );
String mco = normaliseCmdLine( meta.get( Property.main_CodeWriterOptions ) );
t.track( "cmd line", co );
t.track( "prev cmd line", mco );
if( !co.equals( mco ) ) {
iUserMessageHandler.debug( "Not uptodate:", "command line was different from previous generate." );
uptodate = false;
proceed = false;
}
}
// test resource files
if( proceed ) {
RootBuildResource rbr = new RootBuildResource();
String[] generatedFiles = meta.getList( Property.jostraca_GeneratedFiles, Standard.COMMA );
int numGF = generatedFiles.length;
// this assumes that we actually want to generate files, not just compile the template
// however, the CodeBuilder should ensure that no unnecessary compilation is done in any case
// this logic will probably cause unnecessary reparsing of the template, but it's good to err
// on the side of regenerating to avoid false negatives
if( 0 == numGF ) {
iUserMessageHandler.debug( "Not uptodate:", "no files generated previously." );
uptodate = false;
}
// there were previously generated files, so we can test the resources against them for uptodateness
else {
String[] tm_resourceFiles = meta.getList( Property.jostraca_FileBuildResources, Standard.COMMA );
int num_tmRF = tm_resourceFiles.length;
File[] cmd_resourceFiles = parseResourceFiles( tmps.get( Property.main_CodeWriterOptions ),
tmps.get( Property.main_WorkFolder ) );
int num_cmdRF = cmd_resourceFiles.length;
// check against template
for( int gfI = 0; gfI < numGF; gfI++ ) {
FileBuildResource fbr = new FileBuildResource( new File( tp.getTemplatePath() ),
new File( generatedFiles[gfI] ) );
rbr.add( fbr );
}
// check against template resources
for( int rfI = 0; rfI < num_tmRF; rfI++ ) {
for( int gfI = 0; gfI < numGF; gfI++ ) {
FileBuildResource fbr = new FileBuildResource( new File( tm_resourceFiles[rfI] ),
new File( generatedFiles[gfI] ) );
rbr.add( fbr );
}
}
// check against cmd resources
for( int rfI = 0; rfI < num_cmdRF; rfI++ ) {
for( int gfI = 0; gfI < numGF; gfI++ ) {
FileBuildResource fbr = new FileBuildResource( cmd_resourceFiles[rfI],
new File( generatedFiles[gfI] ) );
rbr.add( fbr );
}
}
uptodate = rbr.uptodate();
proceed = false;
if( uptodate ) {
iUserMessageHandler.debug( "Uptodate:", rbr.toString() );
}
else {
iUserMessageHandler.debug( "Not uptodate:", rbr.toString() );
}
}
}
}
t.track( "checkUpToDate", uptodate );
return uptodate;
}
catch( Exception e ) {
ErrorUtil.nonFatalMsg( e );
return false;
}
}
// FIX: convert to List
/** Find the CodeWriter options which are files */
private File[] parseResourceFiles( String pCodeWriterOptions, String pWorkFolder ) {
Vector resourceFilesV = new Vector();
String[] options = ArgUtil.splitQuoted( pCodeWriterOptions );
int numOptions = options.length;
String option = Standard.EMPTY;
File f = null;
for( int optionI = 0; optionI < numOptions; optionI++ ) {
option = options[optionI];
f = null;
// relative to work folder
f = new File( pWorkFolder, option );
if( f.exists() ) {
resourceFilesV.addElement( f );
continue;
}
// absolute path
f = new File( option );
if( f.exists() ) {
resourceFilesV.addElement( f );
continue;
}
}
File[] files = new File[ resourceFilesV.size() ];
resourceFilesV.copyInto( files );
return files;
}
/** Always use forward slash */
private String normaliseCmdLine( String pCmdLine ) {
String cmdline = pCmdLine.replace( Standard.CHAR_BACKSLASH, Standard.CHAR_FORWARDSLASH );
cmdline = cmdline.trim();
return cmdline;
}
private void setRegExpProvider( PropertySet pSystemPS ) {
String repStr = pSystemPS.get( Property.jostraca_RegExpProvider );
try {
Class repClass = Class.forName( repStr );
RegExpProvider rep = (RegExpProvider) repClass.newInstance();
RegExp.setRegExpProvider( rep );
}
catch( Exception e ) {
throw new ServiceException( ServiceException.CODE_invalid_regexpprovider, repStr, e );
}
}
}