View Javadoc
1   /*
2    * (c) Copyright 2006-2020 by rapiddweller GmbH & Volker Bergmann. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, is permitted under the terms of the
6    * GNU General Public License.
7    *
8    * For redistributing this software or a derivative work under a license other
9    * than the GPL-compatible Free Software License as defined by the Free
10   * Software Foundation or approved by OSI, you must first obtain a commercial
11   * license to this software product from rapiddweller GmbH & Volker Bergmann.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
15   * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
17   * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGE.
25   */
26  
27  package com.rapiddweller.benerator.gui;
28  
29  import com.rapiddweller.benerator.archetype.FolderLayout;
30  import com.rapiddweller.benerator.main.DBSnapshotTool;
31  import com.rapiddweller.common.CollectionUtil;
32  import com.rapiddweller.common.ConfigurationError;
33  import com.rapiddweller.common.Context;
34  import com.rapiddweller.common.Encodings;
35  import com.rapiddweller.common.FileUtil;
36  import com.rapiddweller.common.IOUtil;
37  import com.rapiddweller.common.OrderedMap;
38  import com.rapiddweller.common.StringUtil;
39  import com.rapiddweller.common.SystemInfo;
40  import com.rapiddweller.common.accessor.GraphAccessor;
41  import com.rapiddweller.common.context.DefaultContext;
42  import com.rapiddweller.common.converter.ToStringConverter;
43  import com.rapiddweller.common.maven.MavenUtil;
44  import com.rapiddweller.common.ui.I18NError;
45  import com.rapiddweller.common.ui.ProgressMonitor;
46  import com.rapiddweller.common.version.VersionInfo;
47  import com.rapiddweller.format.html.parser.DefaultHTMLTokenizer;
48  import com.rapiddweller.format.html.parser.HTMLTokenizer;
49  import com.rapiddweller.format.text.LFNormalizingStringBuilder;
50  import com.rapiddweller.model.data.ComplexTypeDescriptor;
51  import com.rapiddweller.model.data.ComponentDescriptor;
52  import com.rapiddweller.model.data.DataModel;
53  import com.rapiddweller.model.data.FeatureDetail;
54  import com.rapiddweller.model.data.IdDescriptor;
55  import com.rapiddweller.model.data.InstanceDescriptor;
56  import com.rapiddweller.model.data.PartDescriptor;
57  import com.rapiddweller.model.data.ReferenceDescriptor;
58  import com.rapiddweller.model.data.SimpleTypeDescriptor;
59  import com.rapiddweller.model.data.TypeDescriptor;
60  import com.rapiddweller.platform.db.DBSystem;
61  import com.rapiddweller.platform.db.DefaultDBSystem;
62  import com.rapiddweller.script.Expression;
63  import com.rapiddweller.script.expression.ExpressionUtil;
64  
65  import java.io.BufferedReader;
66  import java.io.File;
67  import java.io.FileNotFoundException;
68  import java.io.IOException;
69  import java.text.ParseException;
70  import java.util.ArrayList;
71  import java.util.List;
72  import java.util.Map;
73  import java.util.Set;
74  
75  import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_CONSUMER;
76  import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_NAME;
77  import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_SOURCE;
78  import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_TYPE;
79  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_ATTRIBUTE;
80  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_COMMENT;
81  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_DATABASE;
82  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_EXECUTE;
83  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_GENERATE;
84  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_ID;
85  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_REFERENCE;
86  import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_SETUP;
87  
88  /**
89   * Creates benerator project archetypes.<br/>
90   * Created at 30.11.2008 17:59:18
91   *
92   * @author Volker Bergmann
93   * @since 0.5.6
94   */
95  public class ProjectBuilder implements Runnable {
96  
97    private static final String TAB = "    ";
98    private static final String DBUNIT_SNAPSHOT_FILENAME = "base.dbunit.xml";
99    private static final String COMMENT_SNAPSHOT = "Create a valid predefined base data set for regression testing " +
100       "by importing a snapshot file";
101   private static final String COMMENT_DROP_TABLES = "Drop the current tables and sequences if they already exist";
102   private static final String COMMENT_CREATE_TABLES = "Create the tables and sequences";
103 
104 
105   private static final Set<String> DB_CONSTRAINT_NAMES = CollectionUtil.toSet("nullable", "maxLength", "type");
106   private static final ToStringConverter toStringConverter = new ToStringConverter("");
107 
108   /**
109    * The Setup.
110    */
111   protected final Setup setup;
112   private final List<Exception> errors;
113   private final ProgressMonitor monitor;
114   private final FolderLayout folderLayout;
115   private final DataModel dataModel;
116   /**
117    * The Descriptors.
118    */
119   protected TypeDescriptor[] descriptors;
120   /**
121    * The Db.
122    */
123   protected DBSystem db;
124 
125   /**
126    * Instantiates a new Project builder.
127    *
128    * @param setup        the setup
129    * @param folderLayout the folder layout
130    * @param monitor      the monitor
131    */
132   public ProjectBuilder(Setup setup, FolderLayout folderLayout, ProgressMonitor monitor) {
133     this.setup = setup;
134     this.errors = new ArrayList<>();
135     this.monitor = monitor;
136     this.descriptors = new TypeDescriptor[0];
137     this.folderLayout = folderLayout;
138     this.dataModel = new DataModel();
139   }
140 
141   private static Map<String, String> defineDbAttributes(Setup setup, DefaultHTMLTokenizer tokenizer) {
142     Map<String, String> attributes = tokenizer.attributes();
143     attributes.put("url", setup.getDbUrl());
144     attributes.put("driver", setup.getDbDriver());
145     if (!StringUtil.isEmpty(setup.getDbSchema())) {
146       attributes.put("schema", setup.getDbSchema());
147     }
148     attributes.put("user", setup.getDbUser());
149     if (!StringUtil.isEmpty(setup.getDbPassword())) {
150       attributes.put("password", setup.getDbPassword());
151     }
152     return attributes;
153   }
154 
155   private static void appendStartTag(String nodeName, Map<String, String> attributes,
156                                      LFNormalizingStringBuilder writer, boolean wrapAttribs) {
157     writer.append('<').append(nodeName);
158     writeAttributes(attributes, writer, wrapAttribs);
159     writer.append('>');
160   }
161 
162   private static Map<String, String> defineSetupAttributes(DefaultHTMLTokenizer tokenizer,
163                                                            Setup setup) {
164     Map<String, String> attributes = tokenizer.attributes();
165     if (setup.getEncoding() != null) {
166       attributes.put("defaultEncoding", setup.getEncoding());
167     }
168     if (setup.getDataset() != null) {
169       attributes.put("defaultDataset", setup.getDataset());
170     }
171     if (setup.getLocale() != null) {
172       attributes.put("defaultLocale", setup.getLocale());
173     }
174     if (setup.getLineSeparator() != null) {
175       attributes.put("defaultLineSeparator", StringUtil.escape(setup.getLineSeparator()));
176     }
177     return attributes;
178   }
179 
180   private static void appendElement(String nodeName, Map<String, String> attributes, LFNormalizingStringBuilder writer, boolean wrapAttribs) {
181     writer.append('<').append(nodeName);
182     writeAttributes(attributes, writer, wrapAttribs);
183     writer.append("/>");
184   }
185 
186   private static void writeAttributes(Map<String, String> attributes, LFNormalizingStringBuilder writer, boolean wrapAttribs) {
187     int i = 0;
188     for (Map.Entry<String, String> attribute : attributes.entrySet()) {
189       if (wrapAttribs && i > 0) {
190         writer.append(TAB).append(TAB);
191       } else {
192         writer.append(' ');
193       }
194       writer.append(attribute.getKey()).append("=\"").append(attribute.getValue()).append("\"");
195       if (wrapAttribs && i < attributes.size() - 1) {
196         writer.append('\n');
197       }
198       i++;
199     }
200   }
201 
202   private static void copyToProject(File srcFile, File projectFolder) throws IOException {
203     File dstFile = new File(projectFolder, srcFile.getName());
204     FileUtil.copy(srcFile, dstFile, true);
205   }
206 
207   private static void processComment(DefaultHTMLTokenizer tokenizer, Setup setup,
208                                      LFNormalizingStringBuilder writer) throws IOException, ParseException {
209     String startText = tokenizer.text();
210     int nextToken = tokenizer.nextToken();
211     if (nextToken == HTMLTokenizer.END_TAG) {
212       writer.append(startText).append(tokenizer.text());
213       return;
214     }
215     if (nextToken != HTMLTokenizer.TEXT) {
216       throw new ParseException("Text expected in comment", -1);
217     }
218     String commentText = tokenizer.text().trim();
219     if ((COMMENT_DROP_TABLES.equals(commentText) && setup.getDropScriptFile() == null)
220         || (COMMENT_CREATE_TABLES.equals(commentText) && setup.getCreateScriptFile() == null)
221         || (COMMENT_SNAPSHOT.equals(commentText) && setup.getDbSnapshot() == null)) {
222       while (tokenizer.nextToken() != HTMLTokenizer.END_TAG) {
223         // skip all elements until comment end
224       }
225     } else {
226       // write comment start and content
227       writer.append(startText).append(tokenizer.text());
228     }
229   }
230 
231   private static boolean isDefaultValue(Object value, String name) {
232     return "nullable".equals(name) && (value == null || ((Boolean) value));
233   }
234 
235   private static void appendEndElement(String nodeName, LFNormalizingStringBuilder writer) {
236     writer.append("</").append(nodeName).append(">");
237   }
238 
239   private static void format(FeatureDetail<?> detail, Map<String, String> attributes) {
240     if (!ATT_NAME.equals(detail.getName()) && detail.getValue() != null && !isDbConstraint(detail.getName())) {
241       attributes.put(detail.getName(), toStringConverter.convert(detail.getValue()));
242     }
243   }
244 
245   private static boolean isDbConstraint(String name) {
246     return DB_CONSTRAINT_NAMES.contains(name);
247   }
248 
249   @Override
250   public void run() {
251     try {
252       // read data model
253       if (setup.isDatabaseProject()) {
254         parseDatabaseMetaData();
255         if (monitor != null) {
256           monitor.setMaximum(5 + descriptors.length);
257         }
258         advanceMonitor();
259       } else {
260         monitor.setMaximum(5);
261       }
262 
263       createFolderLayout();
264       applyArchetype(setup, monitor);
265       createPOM();
266       createProjectPropertiesFile();
267 
268       // copy import files
269       copyImportFiles();
270 
271       // create db snapshot project.dbunit.xml
272       Exception exception = null;
273       if (setup.isDatabaseProject() && !"none".equals(setup.getDbSnapshot()) && !setup.isShopProject()) {
274         try {
275           createDbSnapshot();
276         } catch (Exception e) {
277           exception = e;
278         }
279       }
280 
281       // create project.ben.xml (including imports)
282       createBeneratorXml();
283 
284       createEclipseProject();
285 
286       if (exception != null) {
287         throw exception;
288       }
289     } catch (Exception e) {
290       errors.add(e);
291       e.printStackTrace();
292     } finally {
293       if (db != null) {
294         db.close();
295       }
296       if (monitor != null) {
297         monitor.setProgress(monitor.getMaximum());
298       }
299     }
300   }
301 
302   private void parseDatabaseMetaData() {
303     noteMonitor("scanning database");
304     if (monitor != null) {
305       monitor.setProgress(0);
306     }
307     db = getDBSystem(setup);
308     descriptors = db.getTypeDescriptors();
309   }
310 
311   private void createFolderLayout() {
312     String groupId = setup.getGroupId();
313     String pkgFolder = "/" + (StringUtil.isEmpty(groupId) ? "" : groupId.replace('.', '/') + '/') + setup.getProjectName();
314     haveSubFolder("src/main/java" + pkgFolder);
315     haveSubFolder("src/main/resources" + pkgFolder);
316     haveSubFolder("src/test/java" + pkgFolder);
317     haveSubFolder("src/test/resources" + pkgFolder);
318   }
319 
320   private void applyArchetype(Setup setup, ProgressMonitor monitor) throws IOException {
321     // create project files
322     monitor.setNote("Creating files...");
323     setup.getArchetype().copyFilesTo(setup.getProjectFolder(), folderLayout);
324   }
325 
326   private void createEclipseProject() {
327     setup.projectFile(".project"); // call this for existence check and overwrite error
328     if (setup.isEclipseProject()) {
329       noteMonitor("Creating Eclipse project");
330       MavenUtil.invoke("eclipse:eclipse", setup.getProjectFolder(), !setup.isOffline());
331     }
332     advanceMonitor();
333   }
334 
335   private void haveSubFolder(String relativePath) {
336     FileUtil.ensureDirectoryExists(setup.subDirectory(folderLayout.mapSubFolder(relativePath)));
337   }
338 
339   /**
340    * Get errors exception [ ].
341    *
342    * @return the exception [ ]
343    */
344   public Exception[] getErrors() {
345     return CollectionUtil.toArray(errors, Exception.class);
346   }
347 
348   private void advanceMonitor() {
349     if (monitor != null) {
350       monitor.advance();
351     }
352   }
353 
354   private void noteMonitor(String note) {
355     if (monitor != null) {
356       monitor.setNote(note);
357     }
358   }
359 
360   private void copyImportFiles() {
361     if (!setup.isShopProject()) {
362       copyImportFile(setup.getDropScriptFile());
363       copyImportFile(setup.getCreateScriptFile());
364     }
365   }
366 
367   private void copyImportFile(File importFile) {
368     if (importFile == null) {
369       return;
370     }
371     if (importFile.exists()) {
372       noteMonitor("Importing " + importFile);
373       File copy = setup.projectFile(importFile.getName());
374       try {
375         IOUtil.copyFile(importFile.getAbsolutePath(), copy.getAbsolutePath());
376       } catch (IOException e) {
377         throw new I18NError("ErrorCopying", e, importFile.getAbsolutePath(), copy);
378       }
379     } else {
380       errors.add(new I18NError("FileNotFound",
381           new FileNotFoundException(importFile.getAbsolutePath()), importFile));
382     }
383     advanceMonitor();
384   }
385 
386   private void createDbSnapshot() {
387     String format = setup.getDbSnapshot();
388     File file = setup.projectFile(setup.getDbSnapshotFile());
389     DBSnapshotTool.export(setup.getDbUrl(), setup.getDbDriver(), setup.getDbSchema(),
390         setup.getDbUser(), setup.getDbPassword(), file.getAbsolutePath(), setup.getEncoding(), format,
391         null, monitor);
392   }
393 
394   private void createPOM() {
395     noteMonitor("creating pom.xml");
396     resolveVariables(new File(setup.getProjectFolder(), "pom.xml"));
397   }
398 
399   private void createProjectPropertiesFile() {
400     String filename = "benerator.properties";
401     File file = new File(setup.getProjectFolder(), filename);
402     if (file.exists()) {
403       noteMonitor("creating " + filename);
404       resolveVariables(file);
405     }
406   }
407 
408   /**
409    * Resolve variables file.
410    *
411    * @param file the file
412    * @return the file
413    */
414   public File resolveVariables(File file) {
415     try {
416       String content = IOUtil.getContentOfURI(file.getAbsolutePath());
417       content = resolveVariables(content);
418       file.delete();
419       IOUtil.writeTextFile(file.getAbsolutePath(), content, getXMLEncoding());
420       return file;
421     } catch (IOException e) {
422       throw new I18NError("ErrorCreatingFile", e, file);
423     } finally {
424       advanceMonitor();
425     }
426   }
427 
428   private String getXMLEncoding() {
429     String configuredEncoding = setup.getEncoding();
430     return (StringUtil.isEmpty(configuredEncoding) ? Encodings.UTF_8 : configuredEncoding);
431   }
432 
433   private String resolveVariables(String content) {
434     return replaceVariables(content);
435   }
436 
437   /**
438    * Create benerator xml.
439    *
440    * @throws IOException    the io exception
441    * @throws ParseException the parse exception
442    */
443   public void createBeneratorXml() throws IOException, ParseException {
444     File descriptorFile = new File(setup.getProjectFolder(), "benerator.xml");
445     if (descriptorFile.exists()) { // not applicable for XML schema based generation
446       BufferedReader reader = IOUtil.getReaderForURI(descriptorFile.getAbsolutePath());
447       DefaultHTMLTokenizer tokenizer = new DefaultHTMLTokenizer(reader);
448       String lineSeparator = setup.getLineSeparator();
449       if (StringUtil.isEmpty(lineSeparator)) {
450         lineSeparator = SystemInfo.getLineSeparator();
451       }
452       LFNormalizingStringBuilder writer = new LFNormalizingStringBuilder(lineSeparator);
453       while (tokenizer.nextToken() != HTMLTokenizer.END) {
454         processToken(setup, tokenizer, writer);
455       }
456       String xml = writer.toString();
457       xml = resolveVariables(xml);
458       IOUtil.writeTextFile(descriptorFile.getAbsolutePath(), xml, "UTF-8");
459     }
460     monitor.advance();
461   }
462 
463   private void processToken(Setup setup,
464                             DefaultHTMLTokenizer tokenizer, LFNormalizingStringBuilder writer)
465       throws IOException, ParseException {
466 
467     switch (tokenizer.tokenType()) {
468       case HTMLTokenizer.START_TAG: {
469         String nodeName = tokenizer.name();
470         if (EL_SETUP.equals(nodeName)) {
471           appendStartTag(nodeName, defineSetupAttributes(tokenizer, setup), writer, true);
472         } else if (EL_COMMENT.equals(nodeName)) {
473           processComment(tokenizer, setup, writer);
474         } else {
475           writer.append(tokenizer.text());
476         }
477         break;
478       }
479       case HTMLTokenizer.CLOSED_TAG: {
480         String nodeName = tokenizer.name();
481         if (EL_DATABASE.equals(nodeName) && setup.isDatabaseProject()) {
482           appendElement(nodeName, defineDbAttributes(setup, tokenizer), writer, true);
483         } else if (EL_EXECUTE.equals(nodeName)) {
484           Map<String, String> attributes = tokenizer.attributes();
485           String uri = attributes.get("uri");
486           if ("{drop_tables.sql}".equals(uri)) {
487             if (setup.getDropScriptFile() != null) {
488               File dropScriptFile = setup.getDropScriptFile();
489               copyToProject(dropScriptFile, setup.getProjectFolder());
490               attributes.put("uri", dropScriptFile.getName());
491               appendElement(nodeName, attributes, writer, false);
492             }
493           } else if ("{create_tables.sql}".equals(uri)) {
494             if (setup.getCreateScriptFile() != null) {
495               File createScriptFile = setup.getCreateScriptFile();
496               copyToProject(createScriptFile, setup.getProjectFolder());
497               attributes.put("uri", createScriptFile.getName());
498               appendElement(nodeName, attributes, writer, false);
499             }
500           } else {
501             writer.append(tokenizer.text());
502           }
503         } else if (EL_GENERATE.equals(nodeName)) {
504           Map<String, String> attributes = tokenizer.attributes();
505           if (DBUNIT_SNAPSHOT_FILENAME.equals(attributes.get(ATT_SOURCE))) {
506             if (setup.getDbSnapshot() != null) {
507               appendElement(nodeName, attributes, writer, false);
508             }
509           } else if ("tables".equals(attributes.get(ATT_TYPE))) {
510             generateTables(setup, writer);
511           } else {
512             writer.append(tokenizer.text());
513           }
514         } else {
515           writer.append(tokenizer.text());
516         }
517         break;
518       }
519       default:
520         writer.append(tokenizer.text());
521     }
522   }
523 
524   private void generateTables(Setup setup, LFNormalizingStringBuilder writer) {
525     DBSystem db = getDBSystem(setup);
526     TypeDescriptor[] descriptors = db.getTypeDescriptors();
527     for (TypeDescriptor descriptor : descriptors) {
528       ComplexTypeDescriptorddweller/model/data/ComplexTypeDescriptor.html#ComplexTypeDescriptor">ComplexTypeDescriptor complexType = (ComplexTypeDescriptor) descriptor;
529       final String name = complexType.getName();
530       InstanceDescriptorcriptor.html#InstanceDescriptor">InstanceDescriptor iDesc = new InstanceDescriptor(name, db, complexType);
531       if (setup.getDbSnapshot() != null) {
532         iDesc.setCount(ExpressionUtil.constant(0L));
533       } else {
534         iDesc.setCount(ExpressionUtil.constant(db.countEntities(name)));
535       }
536       generateTable(iDesc, writer);
537     }
538   }
539 
540   private DBSystem getDBSystem(Setup setup) {
541     DBSystem db = new DefaultDBSystem("db", setup.getDbUrl(), setup.getDbDriver(), setup.getDbUser(), setup.getDbPassword(), dataModel);
542     if (setup.getDbSchema() != null) {
543       db.setSchema(setup.getDbSchema());
544     }
545     dataModel.addDescriptorProvider(db);
546     return db;
547   }
548 
549   @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
550   private void generateTable(InstanceDescriptor descriptor, LFNormalizingStringBuilder writer) {
551     ComplexTypeDescriptorom/rapiddweller/model/data/ComplexTypeDescriptor.html#ComplexTypeDescriptor">ComplexTypeDescriptor type = (ComplexTypeDescriptor) descriptor.getTypeDescriptor();
552     Map<String, String> attributes = new OrderedMap<>();
553     for (FeatureDetail<?> detail : descriptor.getDetails()) {
554       Object value = detail.getValue();
555       if (value != null && !isDefaultValue(value, detail.getName())) {
556         if (value instanceof Expression) {
557           value = ((Expression<?>) value).evaluate(null);
558         }
559         attributes.put(detail.getName(), toStringConverter.convert(value));
560       }
561     }
562     attributes.put(ATT_CONSUMER, "db");
563     appendStartTag(EL_GENERATE, attributes, writer, false);
564     writer.append('\n');
565 
566     for (ComponentDescriptor cd : type.getComponents()) {
567       addAttribute(cd, writer);
568     }
569     writer.append(TAB);
570     appendEndElement(EL_GENERATE, writer);
571     writer.append("\n\n").append(TAB);
572   }
573 
574   private void addAttribute(ComponentDescriptor component, LFNormalizingStringBuilder writer) {
575     // normalize
576     boolean nullable = (component.isNullable() == null || component.isNullable());
577     if (component.getMaxCount() != null && component.getMaxCount().evaluate(null) == 1) {
578       component.setMaxCount(null);
579     }
580     if (component.getMinCount() != null && component.getMinCount().evaluate(null) == 1) {
581       component.setMinCount(null);
582     }
583     if (nullable) {
584       component.setNullable(null);
585     }
586 
587     String elementName = null;
588     if (component instanceof PartDescriptor) {
589       elementName = EL_ATTRIBUTE;
590     } else if (component instanceof ReferenceDescriptor) {
591       elementName = EL_REFERENCE;
592     } else if (component instanceof IdDescriptor) {
593       elementName = EL_ID;
594     } else {
595       throw new UnsupportedOperationException("Component descriptor type not supported: " +
596           component.getClass().getSimpleName());
597     }
598 
599     Map<String, String> attributes = new OrderedMap<>();
600     attributes.put(ATT_NAME, component.getName());
601     SimpleTypeDescriptorcom/rapiddweller/model/data/SimpleTypeDescriptor.html#SimpleTypeDescriptor">SimpleTypeDescriptor type = (SimpleTypeDescriptor) (component.getType() != null ?
602         dataModel.getTypeDescriptor(component.getType()) :
603         component.getLocalType());
604     if (type != null) {
605       for (FeatureDetail<?> detail : type.getDetails()) {
606         format(detail, attributes);
607       }
608     }
609     for (FeatureDetail<?> detail : component.getDetails()) {
610       format(detail, attributes);
611     }
612     if (nullable) {
613       attributes.put("nullQuota", "1");
614     }
615 
616     writer.append(TAB).append(TAB).append("<!--").append(elementName);
617     for (Map.Entry<String, String> entry : attributes.entrySet()) {
618       writer.append(' ').append(entry.getKey()).append("=\"").append(entry.getValue()).append('"');
619     }
620     writer.append(" /-->");
621     writer.append('\n');
622   }
623 
624   private String replaceVariables(String text) {
625     int varStart = 0;
626     Context context = new DefaultContext();
627     context.set("setup", setup);
628     context.set("version", VersionInfo.getInfo("benerator"));
629     while ((varStart = text.indexOf("${", varStart)) >= 0) {
630       int varEnd = text.indexOf("}", varStart);
631       if (varEnd < 0) {
632         throw new ConfigurationError("'${' without '}'");
633       }
634       String template = text.substring(varStart, varEnd + 1);
635       String path = template.substring(2, template.length() - 1).trim();
636       GraphAccessor accessor = new GraphAccessor(path);
637       Object varValue = accessor.getValue(context);
638       String varString = toStringConverter.convert(varValue);
639       if (!StringUtil.isEmpty(varString)) {
640         text = text.replace(template, varString);
641       }
642       varStart = varEnd;
643     }
644     text = text.replace("\n        defaultEncoding=\"\"", "");
645     text = text.replace("\n        defaultDataset=\"\"", "");
646     text = text.replace("\n        defaultLocale=\"\"", "");
647     text = text.replace("\n        defaultLineSeparator=\"\"", "");
648     return text;
649   }
650 
651 }