Index: topia2/src/java/org/codelutin/topia/generator/TapestryWebGenerator.java diff -u /dev/null topia2/src/java/org/codelutin/topia/generator/TapestryWebGenerator.java:1.1 --- /dev/null Tue May 29 10:08:38 2007 +++ topia2/src/java/org/codelutin/topia/generator/TapestryWebGenerator.java Tue May 29 10:08:33 2007 @@ -0,0 +1,592 @@ +/* *##% + * Copyright (C) 2007 Code Lutin + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *##%*/ + +package org.codelutin.topia.generator; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.codelutin.generator.MonitorWriter; +import org.codelutin.generator.StateModelGenerator; +import org.codelutin.generator.Util; +import org.codelutin.generator.models.state.StateModel; +import org.codelutin.generator.models.state.StateModelComplexState; +import org.codelutin.generator.models.state.StateModelSimpleState; +import org.codelutin.generator.models.state.StateModelState; +import org.codelutin.generator.models.state.StateModelStateChart; +import org.codelutin.generator.models.state.StateModelTransition; + +/** + * Genere la navigation basee sur le framework Tapestry 5 + * a partir de la representation StateModel + * + * @see org.codelutin.generator.StateModelGenerator + * @author chatellier + * @version $Revision: 1.1 $ + * + * Last update : $Date: 2007/05/29 10:08:33 $ + * By : $Author: chatellier $ + */ +public class TapestryWebGenerator extends StateModelGenerator { + + /** logger */ + private static final Log log = LogFactory.getLog(TapestryWebGenerator.class); + + /** Already asssigned use case states names */ + protected List assignedUseCaseStateNames; + + /** + * Constructeur + * @param parent Generateur parent + */ + public TapestryWebGenerator() { + assignedUseCaseStateNames = new ArrayList(); + } + + /** + * Redefintion pour une generation specifique tapestry + * + * @param stateModel Le modele d'état + * @param destDir le dossier de destination + */ + public void generate(StateModel stateModel, File destDir) throws IOException { + + // pour tous les diagramme du model + for (StateModelStateChart chart : stateModel.getStateCharts()) { + generate(chart,chart.getStates(),destDir); + + // generate use case engine + generateUseCaseEngineFromModel(stateModel, chart,destDir); + } + } + + /** + * Appele par lutin generator pour tous les etats du model + * @param chart + * @return + */ + protected String getFilenameFromState(StateModelStateChart chart,StateModelState state) { + return (getPackageFromState(chart) + '.' + getNameFromState(state)).replace('.', + File.separatorChar) + ".java"; + } + + /** + * Get generated class name from state + * + * Prefix it with "Abstract" + * + * @param state state + * @return name + */ + protected String getNameFromState(StateModelState state) { + return "Abstract" + Util.toUpperCaseFirstLetter(state.getName()); + } + + /** + * Return specifique tapestry package name. + * + * Detect "web" patern and replace it with "web.pages". + * + * @param chart the chart + * @return a tapestry package name + */ + protected String getPackageFromState(StateModelStateChart chart) { + return chart.getPackageName().replaceFirst("web", "web.pages"); + } + + /** + * Return specifique tapestry package name (base) + * + * Detect "web.*" patern and replace it with "web.base" + * Component are placed into "base" directory + * + * @param chart the chart + * @return a tapestry package name + */ + protected String getPackageFromComponents(StateModelStateChart chart) { + return chart.getPackageName().replaceFirst("web.*", "web.base"); + } + + /** + * Generate a collection of states + * + * @param chart the chart + * @param states states collection + * @throws IOException + */ + protected void generate(StateModelStateChart chart, Collection states, File destDir) throws IOException { + + // et tous les états de ces diagrammes + for (Object oState : states.toArray()) { + generateFromState(chart,(StateModelState)oState,destDir); + } + } + + /** + * Generate a state. + * + * This state can be complexe, so the method is recusively called. + * + * @param chart the parent chart + * @param state the current state + * @throws IOException + */ + protected void generateFromState(StateModelStateChart chart, StateModelState state, File destDir) throws IOException { + + // complexe + if(state.isComplex()) { + StateModelComplexState complexeState = (StateModelComplexState)state; + generate(chart,complexeState.getStates(), destDir); + } + // simple + else { + + StateModelSimpleState simpleState = (StateModelSimpleState)state; + + // les etat initiaux et finaux ne donne pas + // pas de generation de fichier + if(!simpleState.isFinal() && !simpleState.isInitial()) { + + String filename = getFilenameFromState(chart, simpleState); + File outputFile = getDestinationFile(destDir, filename); + if (getOverwrite() || !isNewerThanSource(outputFile)) { + try { + StringWriter out = new StringWriter(); + MonitorWriter monitorOut = new MonitorWriter(out); + generateFromSimpleState(monitorOut, chart, simpleState); + write(outputFile, monitorOut); + } catch (Exception eee) { + log.warn("Erreur lors de la génération du fichier " + + outputFile); + throw new RuntimeException( + "Erreur lors de la génération du fichier " + + outputFile, eee); + } + } + } // init && final + } + } + + /** + * Called for each simple state + * + * @param output writer out + * @param chart chart + * @param state simple state + * @throws IOException + */ + protected void generateFromSimpleState(Writer output, StateModelStateChart chart, StateModelState state) throws IOException { + /*{// Automatically generated by LutinGenerator +package <%=getPackageFromState(chart)%>; + +import org.apache.tapestry.annotations.InjectPage; +import org.codelutin.test.web.base.UseCasePage; + +/* + * State <%=getNameFromState(state)%> + *) +public abstract class <%=getNameFromState(state)%> extends UseCasePage { +}*/ + + // generate events + generateInjectionAndEventsFromState(output,chart,state); + + // generate use case name + generateUseCaseNameFromState(output,chart,state); +/*{}}*/ + } + + /** + * Generate state injections + * @param output + * @param chart + * @param state + */ + @SuppressWarnings("unused") + protected void generateInjectionAndEventsFromState(Writer output, StateModelStateChart chart, StateModelState state) throws IOException { + + for(StateModelTransition transition : state.getTransitions()) { + + StateModelState toState = transition.getDestinationState(); + + if(toState.isComplex()) { + StateModelState toInitState = ((StateModelComplexState)toState).getInitialState(); + + // l'attribute doit etre private + // sinon tapestry ne le traite pas +/*{ + /* linked state "<%=toState.getName()%>" *) + @InjectPage + private <%=Util.toUpperCaseFirstLetter(toInitState.getName())%> <%=Util.toLowerCaseFirstLetter(toInitState.getName())%>; +}*/ + // l'attribut etant prive, il faut un getter +/*{ + /* getter for state state "<%=toState.getName()%>" *) + protected <%=Util.toUpperCaseFirstLetter(toInitState.getName())%> get<%=Util.toUpperCaseFirstLetter(toInitState.getName())%>() { + return <%=Util.toLowerCaseFirstLetter(toInitState.getName())%>; + } +}*/ + +/*{ + /* transition on <%=transition.getEvent()%> event *) + public Object onActionFrom<%=Util.toUpperCaseFirstLetter(transition.getEvent())%>() { + enterUseCase(); + return <%=Util.toLowerCaseFirstLetter(toInitState.getName())%>; + } +}*/ + } + else { + + StateModelSimpleState simpleToState = (StateModelSimpleState)toState; + + // si l'etat a injecter n'est pas final + if(!simpleToState.isFinal()) { +/*{ + /* linked state "<%=simpleToState.getName()%>" *) + @InjectPage + private <%=Util.toUpperCaseFirstLetter(simpleToState.getName())%> <%=Util.toLowerCaseFirstLetter(simpleToState.getName())%>; +}*/ + // l'attribut etant prive, il faut un getter +/*{ + /* getter for state state "<%=simpleToState.getName()%>" *) + protected <%=Util.toUpperCaseFirstLetter(simpleToState.getName())%> get<%=Util.toUpperCaseFirstLetter(simpleToState.getName())%>() { + return <%=Util.toLowerCaseFirstLetter(simpleToState.getName())%>; + } +}*/ + } // isFinal +/*{ + /* transition on "<%=transition.getEvent()%>" event *) + public Object onActionFrom<%=Util.toUpperCaseFirstLetter(transition.getEvent())%>() { +}*/ + if(simpleToState.isFinal()) { +/*{ return leaveUseCase(); +}*/ + } else { +/*{ return <%=Util.toLowerCaseFirstLetter(simpleToState.getName())%>; +}*/ + } +/*{ } +}*/ + } + } + } + + /** + * Generate use case name + * + * @param output + * @param chart + * @param state + */ + @SuppressWarnings("unused") + protected void generateUseCaseNameFromState(Writer output, StateModelStateChart chart, StateModelState state) throws IOException { + + String stateId = generateStateUseCaseName(state.getName()); +/*{ + /* return a unique state id for model *) + protected final String getUseCaseName() { + return "<%=stateId%>"; + } +}*/ + } + + /** + * Genere un nom unique d'etat pour le model courant. + * + * Pour des raisons de debugage, on utilise des lettres particuliere du + * nom de l'etat, suivit d'un numero, s'il y a collision. + * + * @return a unique name + */ + protected String generateStateUseCaseName(String name) { + + String ucsn = getHashStateName(name); + + // test already assigned + String uniqueucsn = ucsn; + int num = 2; + while(assignedUseCaseStateNames.contains(uniqueucsn)) { + uniqueucsn = new StringBuffer(ucsn).append(num++).toString(); + } + + assignedUseCaseStateNames.add(uniqueucsn); + + return uniqueucsn; + } + + /** + * Hash a state name. + * + * On prend la premiere lettre de chaque mot. + * + * ContactList -> cl + * contactList -> cl + * ContactListPage -> clp + * + * @param name + * @return + */ + protected String getHashStateName(String name) { + + // bug si moins de 1 caractere + if(name == null || name.length()<1) return name; + + StringBuffer sb = new StringBuffer(); + + // on prend la premiere lettre + sb.append(Character.toLowerCase(name.charAt(0))); + + // ensuite, on prend les majuscules + for(int i = 1 ; i < name.length() ; ++i) { + + // si maj + if(Character.isUpperCase(name.charAt(i))) { + sb.append(Character.toLowerCase(name.charAt(i))); + } + } + + return sb.toString(); + } + + /** + * Generate the engine use case class. + * + * Need to be generated inside client webapp directory to be analysed by + * tapestry (annotations) + * + * @param model model + * @param chart chart + */ + protected void generateUseCaseEngineFromModel(StateModel model,StateModelStateChart chart,File destDir) throws IOException { + String componentPackageName = getPackageFromComponents(chart); + + // UseCasePage + String filename = (componentPackageName + ".UseCasePage").replace('.', File.separatorChar) + ".java"; + File outputFile = getDestinationFile(destDir, filename); + if (getOverwrite() || !isNewerThanSource(outputFile)) { + try { + StringWriter out = new StringWriter(); + MonitorWriter monitorOut = new MonitorWriter(out); + // generate UseCasePage class + generateUseCasePageClass(monitorOut,model,componentPackageName); + write(outputFile, monitorOut); + } catch (Exception e) { + log.warn("Erreur lors de la génération du fichier UseCasePage",e); + throw new RuntimeException( + "Erreur lors de la génération du fichier UseCasePage", e); + } + } + + // UseCaseMap + filename = (componentPackageName + ".UseCaseMap").replace('.', File.separatorChar) + ".java"; + outputFile = getDestinationFile(destDir, filename); + if (getOverwrite() || !isNewerThanSource(outputFile)) { + try { + StringWriter out = new StringWriter(); + MonitorWriter monitorOut = new MonitorWriter(out); + // generate UseCasePage class + generateUseCaseMapClass(monitorOut,componentPackageName); + write(outputFile, monitorOut); + } catch (Exception e) { + log.warn("Erreur lors de la génération du fichier UseCaseMap",e); + throw new RuntimeException( + "Erreur lors de la génération du fichier UseCaseMap", e); + } + } + } + + /** + * Generate base.UseCasePage class + * @param output + * @param componentPackageName + */ + private void generateUseCasePageClass(MonitorWriter output, StateModel model, String componentPackageName) throws IOException { +/*{package <%=componentPackageName%>; + +import org.apache.tapestry.Link; +import org.apache.tapestry.annotations.ApplicationState; +import org.apache.tapestry.annotations.Inject; +import org.apache.tapestry.annotations.Service; +import org.apache.tapestry.internal.services.LinkFactory; +import org.apache.tapestry.internal.services.LinkFactoryListener; +import org.apache.tapestry.services.Request; + +public abstract class UseCasePage}*/ + + // ajoute un heritage si il a ete specifie dans un fichier de propriete + Map tagValues = model.getTagValues(); + if(tagValues != null) { + String tagUseCaseEngineExtendedClass = tagValues.get("usecaseengineextendedclass"); + if(tagUseCaseEngineExtendedClass != null) { + /*{ extends <%=tagUseCaseEngineExtendedClass%>}*/ + } + } + +/*{ implements LinkFactoryListener { + + private static final String UC_PARAMETER_NAME = "UC"; + private static final String UC_PARAMETER_SEPARATOR = ":"; + + @ApplicationState + private UseCaseMap useCaseManager; + private boolean useCaseManagerExists; + + @Inject + @Service("LinkFactory") + private LinkFactory _linkFactory; + + @Inject + @Service("Request") + private Request _request; + + private String UCName; + + public void onActivate() { + _linkFactory.addListener(this); + + UCName = _request.getParameter(UC_PARAMETER_NAME); + } + + protected abstract String getUseCaseName(); + + /** + * Create stack if not exists + *) + protected void init() { + if(!useCaseManagerExists) { + useCaseManager = new UseCaseMap(); + } + } + /** + * Enter in a new sub use case + *) + protected void enterUseCase() { + init(); + // current + String currentUCN = UCName; + // new + UCName = (currentUCN == null) ? getUseCaseName() : currentUCN + UC_PARAMETER_SEPARATOR + getUseCaseName(); + useCaseManager.enterSubUseCase(UCName, currentUCN, this); + } + + /** + * Leave a sub use case + *) + protected UseCasePage leaveUseCase() { + init(); + + UseCasePage nextPage = null; + + // current UCN + String UC = UCName; + + // get state + Object[] response = useCaseManager.leaveUseCase(UC); + + // if null, return null, don't change page + if(response != null) { + // get UCN before enter this one + UCName = (String) response[0]; + + nextPage = (UseCasePage) response[1]; + } + + return nextPage; + } + + /* (non-Javadoc) + * @see org.apache.tapestry.internal.services.LinkFactoryListener#createdActionLink(org.apache.tapestry.Link) + *) + public void createdActionLink(Link _link) { + addUCParameter(_link); + } + + /* (non-Javadoc) + * @see org.apache.tapestry.internal.services.LinkFactoryListener#createdPageLink(org.apache.tapestry.Link) + *) + public void createdPageLink(Link _link) { + // PageLink englobe ausssi les redirects envoyés au client apres une + // action + addUCParameter(_link); + } + + protected void addUCParameter(Link _link) { + if(_link.getParameterValue(UC_PARAMETER_NAME) == null) { + + if(UCName != null) { + _link.addParameter(UC_PARAMETER_NAME, UCName); + } + } + } +} +}*/ + } + + /** + * Generate base.UseCaseMap class + * @param output + * @param componentPackageName + */ + private void generateUseCaseMapClass(MonitorWriter output, String componentPackageName) throws IOException { +/*{package <%=componentPackageName%>; + +import java.util.HashMap; +import java.util.Map; + +public class UseCaseMap { + + /** + * Map (state name -> state instance) + *) + private Map mapUseCase; + + /** + * Constructor + *) + public UseCaseMap () { + mapUseCase = new HashMap(); + } + + /** + * New sub use case + *) + public void enterSubUseCase(String newUseCaseName, String previousUseCaseName, Object state) { + mapUseCase.put(newUseCaseName,new Object[]{previousUseCaseName,state}); + } + + /** + * Finish a subusecase + * + * Return the state that init the subuse case + * @return a state + *) + public Object[] leaveUseCase(String useCaseName) { + return mapUseCase.get(useCaseName); + } +} +}*/ + } +}