001: /*
002: * * transformica 2
003: * Code generator
004: * Copyright (C) 2004 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.pavelvlasov.com/pv/content/menu.show@id=products.transformica.html
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.transformica;
024:
025: import java.io.BufferedWriter;
026: import java.io.File;
027: import java.io.FileWriter;
028: import java.io.IOException;
029: import java.io.StringWriter;
030: import java.io.Writer;
031: import java.util.Collection;
032: import java.util.HashMap;
033: import java.util.HashSet;
034: import java.util.Iterator;
035: import java.util.LinkedList;
036: import java.util.List;
037: import java.util.Map;
038: import java.util.Set;
039:
040: import ognl.Ognl;
041: import ognl.OgnlException;
042:
043: import org.apache.commons.jxpath.JXPathContext;
044: import org.apache.tools.ant.BuildException;
045: import org.apache.velocity.Template;
046: import org.apache.velocity.VelocityContext;
047: import org.apache.velocity.app.Velocity;
048:
049: import biz.hammurapi.CarryOverException;
050: import biz.hammurapi.transformica.helpers.OgnlEvaluator;
051: import biz.hammurapi.util.CompositeAcceptor;
052: import biz.hammurapi.util.Identifiable;
053: import biz.hammurapi.util.PoliteVisitor;
054: import biz.hammurapi.util.Visitable;
055: import biz.hammurapi.util.Visitor;
056:
057: /**
058: * Transformation channel. Matches UML elements by type, stereotype and
059: * generation language. One UML element can match more than one channel.
060: * @ant.element name="channel"
061: *
062: * @author Pavel Vlasov
063: * @version $Revision: 1.1 $
064: */
065: public class Channel implements PoliteVisitor {
066: public static final Set RESERVED_NAMES = new HashSet();
067:
068: static {
069: RESERVED_NAMES.add("element");
070: RESERVED_NAMES.add("session");
071: RESERVED_NAMES.add("jxpath");
072: RESERVED_NAMES.add("ognl");
073: }
074:
075: public static void validateEntryName(String name)
076: throws BuildException {
077: if (RESERVED_NAMES.contains(name)) {
078: throw new BuildException("'" + name
079: + "' context entry name is reserved.");
080: }
081: }
082:
083: private String template;
084: private Template theTemplate;
085: private File outputDir;
086: private String fileNameTemplate;
087: private String name;
088: private boolean hidden = false;
089:
090: /**
091: * @ant.ignore
092: *
093: * @return File name template
094: */
095: public String getFileNameTemplate() {
096: return fileNameTemplate;
097: }
098:
099: /**
100: * File name template. The template is evaluated by the same engine as the
101: * template, all context objects are available during evaluation.
102: * @ant.required
103: *
104: * @param fileNameTemplate
105: */
106: public void setFileNameTemplate(String fileNameTemplate) {
107: this .fileNameTemplate = fileNameTemplate;
108: }
109:
110: /**
111: * @ant.ignore
112: *
113: * @return
114: */
115: public File getOutputDir() {
116: return outputDir;
117: }
118:
119: /**
120: * Output directory
121: * @ant.required
122: *
123: * @param outputDir
124: */
125: public void setOutputDir(File outputDir) throws BuildException {
126: if (!(outputDir.exists() && outputDir.isDirectory())) {
127: throw new BuildException(
128: "Output directory does not exist or not a directory: "
129: + outputDir.getAbsolutePath());
130: }
131: this .outputDir = outputDir;
132: }
133:
134: private boolean append = false;
135:
136: /**
137: * Set this to true to append to existing file instead of overwriting
138: * @ant.non-required
139: *
140: * @param append
141: */
142: public void setAppend(boolean append) {
143: this .append = append;
144: }
145:
146: /**
147: * Template name. If template name is not set then no generation will
148: * happen, but nested channels will be processed.
149: * @ant.non-required
150: *
151: * @param template
152: */
153: public void setTemplate(String template) {
154: this .template = template;
155: }
156:
157: /**
158: * @return
159: */
160: Template getTemplate() throws Exception {
161: if (theTemplate == null) {
162: theTemplate = Velocity.getTemplate(template);
163: }
164: return theTemplate;
165: }
166:
167: private boolean override = true;
168:
169: /**
170: * @return
171: */
172: public boolean isOverride() {
173: return override;
174: }
175:
176: /**
177: * Do not process this channel if model element has already been processed
178: * by another channel. Default is true.
179: * @ant.non-required
180: *
181: * @param override
182: */
183: public void setOverride(boolean override) {
184: this .override = override;
185: }
186:
187: private Map contextEntries = new HashMap();
188:
189: public Map getContextEntries() {
190: return contextEntries;
191: }
192:
193: /**
194: * Context entry which will be available in templates. Overrides entry with
195: * the same name defined at task level.
196: * @ant.non-required
197: *
198: * @param param
199: * @throws BuildException
200: */
201: public void addConfiguredContextEntry(ContextObjectEntry entry)
202: throws BuildException {
203: if (entry.getName() == null) {
204: throw new BuildException("Context entry name is null");
205: }
206:
207: validateEntryName(entry.getName());
208:
209: contextEntries.put(entry.getName(), entry.getObject(null));
210: }
211:
212: private LinkedList acceptors = new LinkedList();
213:
214: /**
215: * Model element acceptor.
216: * @ant.non-required
217: *
218: * @param param
219: * @throws BuildException
220: */
221: public void addConfiguredAcceptor(AcceptorEntry entry)
222: throws BuildException {
223: acceptors.add(entry.getObject(null));
224: }
225:
226: private boolean force = false;
227:
228: /**
229: * @return
230: */
231: public boolean isForce() {
232: return force;
233: }
234:
235: /**
236: * Force generation. Ignore timestamps.
237: * @ant.non-required
238: *
239: * @param force
240: */
241: public void setForce(boolean force) {
242: this .force = force;
243: }
244:
245: private TouchDetector touchDetector;
246:
247: TouchDetector getTouchDetector() {
248: if (touchDetector == null) {
249: if (parent == null) {
250: return session.getTask().getTouchDetector();
251: } else {
252: return parent.getTouchDetector();
253: }
254: } else {
255: return touchDetector;
256: }
257: }
258:
259: /**
260: * File info file. Overrides setting at task level.
261: * @ant.non-required
262: *
263: * @param ftsrf
264: * @throws BuildException
265: */
266: public void addConfiguredFileTouchDetector(
267: FileTouchDetectorFactory ftsrf) throws BuildException {
268: if (touchDetector != null) {
269: throw new BuildException("TimeStamp repository already set");
270: }
271: touchDetector = ftsrf.newTimeStampRepo();
272: }
273:
274: /**
275: * JDBC file info repository. Overrides setting at task level.
276: * @ant.non-required
277: *
278: * @param jtsrf
279: * @throws BuildException
280: */
281: public void addConfiguredJdbcTouchDetector(
282: HypersonicTouchDetectorFactory jtsrf) throws BuildException {
283: if (touchDetector != null) {
284: throw new BuildException("TimeStamp repository already set");
285: }
286: touchDetector = jtsrf.newTimeStampRepo();
287: }
288:
289: protected TransformSession session;
290:
291: private String iteratorExpression;
292:
293: /**
294: * Iterator XPath expression. If iteratorExpression is set then Channel
295: * doesn't process the matched element but applies XPath expression to the
296: * elment and processes results.
297: * @ant.non-required
298: *
299: * @param iteratorExpression
300: */
301: public void setIteratorExpression(String iteratorExpression) {
302: this .iteratorExpression = iteratorExpression;
303: }
304:
305: private String conditionExpression;
306:
307: /**
308: * Condition XPath expression. Must evaluate to true for channel to match.
309: * This is a more advanced then matching by stereotype or element name and
310: * not that complicates as writing custom Acceptor class.
311: * @ant.non-required
312: *
313: * @param stereotype
314: */
315: public void setConditionExpression(String conditionExpression) {
316: this .conditionExpression = conditionExpression;
317: }
318:
319: /**
320: * @param task
321: */
322: public Channel(TransformSession session) {
323: this .session = session;
324: }
325:
326: public void init() throws TransformicaException {
327: if (touchDetector != null) {
328: touchDetector.init();
329: }
330:
331: Iterator it = contextEntries.values().iterator();
332: while (it.hasNext()) {
333: Object o = it.next();
334: if (o instanceof ContextObject) {
335: ((ContextObject) o).init(session);
336: }
337: }
338: }
339:
340: public void destroy() throws TransformicaException {
341: if (touchDetector != null) {
342: touchDetector.destroy();
343: }
344:
345: Iterator it = contextEntries.values().iterator();
346: while (it.hasNext()) {
347: Object o = it.next();
348: if (o instanceof ContextObject) {
349: ((ContextObject) o).destroy();
350: }
351: }
352:
353: Iterator chit = channels.iterator();
354: while (chit.hasNext()) {
355: ((Channel) chit.next()).destroy();
356: }
357: }
358:
359: private CompositeAcceptor compositeAcceptor;
360:
361: public CompositeAcceptor getCompositeAcceptor() {
362: if (compositeAcceptor == null) {
363: Collection c = new LinkedList();
364:
365: if (conditionExpression != null) {
366: c.add(new ConditionExpressionAcceptor(
367: conditionExpression));
368: }
369:
370: c.addAll(acceptors);
371: compositeAcceptor = new CompositeAcceptor(c);
372: }
373: return compositeAcceptor;
374:
375: }
376:
377: /**
378: * @return
379: * @ant.ignore
380: */
381: public String getName() {
382: return name;
383: }
384:
385: /**
386: * Channel name.
387: * @ant.non-required
388: *
389: * @param name
390: */
391: public void setName(String name) {
392: this .name = name;
393: }
394:
395: protected Channel parent;
396:
397: private Map getCascadeContextEntries() {
398: Map ret = new HashMap();
399:
400: if (parent != null) {
401: ret.putAll(parent.getCascadeContextEntries());
402: }
403:
404: ret.putAll(getContextEntries());
405: return ret;
406: }
407:
408: protected List channels = new LinkedList();
409:
410: /**
411: * Nested transformation channel. Nested channel inherits parent's context
412: * objects, applies only to model elements matched by the parent or below
413: * and matches only if parent matches and nested channel matches. Can be
414: * useful for, say, generating only classes from particular packages.
415: * @ant.required
416: *
417: * @return
418: */
419: public Channel createChannel() {
420: Channel ret = new Channel(session);
421: channels.add(ret);
422: ret.parent = this ;
423: return ret;
424: }
425:
426: public void process(Object element, Writer writer)
427: throws TransformicaException {
428: try {
429: session
430: .verbose("Generating: "
431: + getObjectIdString(element));
432:
433: VelocityContext context = session.newContext();
434: Map entries = new HashMap();
435: try {
436: prepareContext(element, context, entries);
437:
438: getTemplate().merge(context, writer);
439: session.info("\tGenerated");
440: } finally {
441: releaseContext(context, entries);
442: }
443: } catch (Exception e) {
444: if (element instanceof Identifiable) {
445: session.error("Exception " + e + " while processing "
446: + getObjectIdString(element));
447: }
448: throw new TransformicaException(e);
449: }
450:
451: }
452:
453: static String getObjectIdString(Object o) {
454: return o instanceof Identifiable ? ((Identifiable) o)
455: .getIdString() : o.getClass().getName();
456: }
457:
458: public String generateFileName(Object element)
459: throws TransformicaException {
460: if (fileNameTemplate == null) {
461: return null;
462: } else {
463: try {
464: VelocityContext context = session.newContext();
465: Map entries = new HashMap();
466: try {
467: prepareContext(element, context, entries);
468: StringWriter sw = new StringWriter();
469: Velocity.evaluate(context, sw,
470: getObjectIdString(element),
471: fileNameTemplate);
472: sw.close();
473: return sw.toString();
474: } finally {
475: releaseContext(context, entries);
476: }
477: } catch (Exception e) {
478: session.error("Exception " + e
479: + " while generating file name for "
480: + getObjectIdString(element));
481: throw new TransformicaException(e);
482: }
483: }
484: }
485:
486: private void releaseContext(VelocityContext context, Map entries)
487: throws TransformicaException {
488: session.releaseContext(context);
489: Iterator coit = entries.values().iterator();
490: while (coit.hasNext()) {
491: Object co = coit.next();
492: if (co instanceof ContextObject) {
493: ((ContextObject) co).unSetContext();
494: }
495: }
496: }
497:
498: private void prepareContext(Object element,
499: VelocityContext context, Map entries) {
500:
501: context.put("element", element);
502: entries.put("element", element);
503:
504: JXPathContext jxPath = JXPathContext.newContext(element);
505: context.put("jxpath", jxPath);
506: entries.put("jxpath", jxPath);
507:
508: OgnlEvaluator ognl = new OgnlEvaluator(element);
509: context.put("ognl", ognl);
510: entries.put("ognl", ognl);
511:
512: context.put("channel", this );
513: entries.put("channel", this );
514:
515: entries.put("session", session);
516:
517: Iterator pit = getCascadeContextEntries().keySet().iterator();
518: while (pit.hasNext()) {
519: String key = (String) pit.next();
520: Object o = getContextEntries().get(key);
521: context.put(key, o);
522: entries.put(key, o);
523: session.debug("Context entry: " + key);
524: }
525:
526: pit = session.getTask().getContextEntries().keySet().iterator();
527: while (pit.hasNext()) {
528: String key = (String) pit.next();
529: if (!entries.keySet().contains(key)) {
530: Object o = session.getTask().getContextEntries().get(
531: key);
532: context.put(key, o);
533: entries.put(key, o);
534: session.debug("Context entry: " + key);
535: }
536: }
537:
538: Iterator coit = entries.entrySet().iterator();
539: while (coit.hasNext()) {
540: Map.Entry entry = (Map.Entry) coit.next();
541: if (entry.getValue() instanceof ContextObject) {
542: ((ContextObject) entry.getValue())
543: .setContext(new Context(element, context,
544: (String) entry.getKey()));
545: }
546: }
547: }
548:
549: /**
550: * Generates channel to String for inclusion to another template.
551: *
552: * @param element
553: * @return @throws
554: * TransformicaException
555: */
556: String include(Object element) throws TransformicaException {
557: try {
558: StringWriter sw = new StringWriter();
559: process(element, sw);
560: sw.close();
561: return sw.toString();
562: } catch (IOException e) {
563: session.error("Exception " + e + " while processing "
564: + getObjectIdString(element));
565: throw new TransformicaException(e);
566: }
567: }
568:
569: /**
570: * Processes and writes output to File
571: *
572: * @param modelElement
573: * @throws TransformicaException
574: */
575: public void process(Object element) throws TransformicaException {
576: try {
577: Writer writer = getConsumer().getWriter(element);
578: if (writer != null) {
579: process(element, writer);
580: writer.close();
581: }
582: } catch (TransformicaException e) {
583: session.error("Exception " + e + " while processing "
584: + getObjectIdString(element));
585: throw e;
586: } catch (Exception e) {
587: session.error("Exception " + e + " while processing "
588: + getObjectIdString(element));
589: throw new TransformicaException(e);
590: }
591: }
592:
593: /**
594: * Override this method to route output
595: * to custom destination.
596: * @return
597: */
598: protected Consumer getConsumer() {
599: return new Consumer() {
600:
601: public Writer getWriter(Object element)
602: throws TransformicaException {
603: final File outFile = new File(getOutputDir(),
604: generateFileName(element));
605:
606: session.info("Output file: "
607: + outFile.getAbsolutePath());
608: if (outFile.getParentFile() != null) {
609: outFile.getParentFile().mkdirs();
610: }
611:
612: TouchDetector touchDetector = getTouchDetector();
613: if (touchDetector != null
614: && !touchDetector.isToBeGenerated(outFile)) {
615: session.info("\tSkipped");
616: return null;
617: } else {
618: try {
619: return new BufferedWriter(new FileWriter(
620: outFile, append)) {
621:
622: public void close() throws IOException {
623: super .close();
624: TouchDetector touchDetector = getTouchDetector();
625: if (touchDetector != null) {
626: try {
627: touchDetector.register(outFile);
628: } catch (TransformicaException e) {
629: throw new CarryOverException(e);
630: }
631: }
632: }
633: };
634: } catch (CarryOverException e) {
635: throw new TransformicaException(e.getCause());
636: } catch (IOException e) {
637: throw new TransformicaException(e);
638: }
639: }
640: }
641: };
642: }
643:
644: /**
645: * @return
646: * @ant.ignore
647: */
648: protected boolean isHidden() {
649: return hidden;
650: }
651:
652: /**
653: * Indicates that this channel is invisible for a visitor. Default is
654: * false. Set it to true if you want channels for include only.
655: * @ant.non-required
656: *
657: * @param hidden
658: */
659: public void setHidden(boolean hidden) {
660: this .hidden = hidden;
661: }
662:
663: private boolean isLeaveChannel;
664:
665: /**
666: * If true then template processing happens in "leave" method instead of "visit" method.
667: * @ant.non-required
668: * @param isLeaveChannel
669: */
670: public void setLeaveChannel(boolean isLeaveChannel) {
671: this .isLeaveChannel = isLeaveChannel;
672: }
673:
674: public boolean visit(Object element) {
675: if (!isLeaveChannel) {
676: session.verbose("Visiting " + getObjectIdString(element));
677: visitOrLeave(element);
678: }
679: return true;
680: }
681:
682: /**
683: * @param element
684: */
685: private void visitOrLeave(Object element) {
686: try {
687: if (getCompositeAcceptor().accept(element)) {
688: if (session.isMatched(element) && !isOverride()) {
689: session.debug("\t\tAlready matched, skipped.");
690: } else {
691: Iterator ceit = contextEntries.values().iterator();
692: while (ceit.hasNext()) {
693: Object ce = ceit.next();
694: if (ce instanceof Visitor) {
695: ((Visitor) ce).visit(element);
696: }
697: }
698:
699: try {
700: if (template != null) {
701: session.setMatched(element);
702: if (iteratorExpression == null) {
703: process(element);
704: } else {
705: Object o;
706: if (element instanceof biz.hammurapi.config.Context) {
707: o = ((biz.hammurapi.config.Context) element)
708: .get(iteratorExpression);
709: } else {
710: try {
711: o = Ognl.getValue(
712: iteratorExpression,
713: element);
714: } catch (OgnlException e1) {
715: throw new TransformicaException(
716: "Cannot evaluate: "
717: + iteratorExpression);
718: }
719: }
720:
721: if (o instanceof Collection) {
722: o = ((Collection) o).iterator();
723: }
724:
725: if (o instanceof Iterator) {
726: Iterator it = (Iterator) o;
727: while (it.hasNext()) {
728: process(it.next());
729: }
730: } else {
731: throw new TransformicaException(
732: element.getClass()
733: .getName()
734: + ": cannot iterate through results of '"
735: + iteratorExpression
736: + "'");
737: }
738: }
739: }
740: } finally {
741: Iterator cceit = contextEntries.values()
742: .iterator();
743: while (cceit.hasNext()) {
744: Object ce = cceit.next();
745: if (ce instanceof PoliteVisitor) {
746: ((PoliteVisitor) ce).leave(element);
747: }
748: }
749: }
750: }
751:
752: Iterator chit = channels.iterator();
753: while (chit.hasNext()) {
754: Channel next = (Channel) chit.next();
755: if (element instanceof Visitable) {
756: ((Visitable) element).accept(next);
757: } else {
758: next.visit(element);
759: next.leave(element);
760: }
761: }
762: } else {
763: session.debug("\t\tRejected");
764: }
765: } catch (TransformicaException e) {
766: session
767: .error("Exception " + e + " ("
768: + e.getStackTrace()[0].toString() + ")"
769: + " while processing "
770: + getObjectIdString(element));
771: throw new TransformicaRuntimeException(e);
772: }
773: }
774:
775: public void leave(Object element) {
776: if (isLeaveChannel) {
777: session.verbose("Leaving " + getObjectIdString(element));
778: visitOrLeave(element);
779: }
780: }
781: }
|