package com.getperka.flatpack.fast;
/*
* #%L
* FlatPack Automatic Source Tool
* %%
* Copyright (C) 2012 - 2013 Perka Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stringtemplate.v4.AttributeRenderer;
import org.stringtemplate.v4.AutoIndentWriter;
import org.stringtemplate.v4.Interpreter;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.STGroupFile;
import org.stringtemplate.v4.misc.ObjectModelAdaptor;
import org.stringtemplate.v4.misc.STNoSuchPropertyException;
import com.getperka.cli.flags.Flag;
import com.getperka.flatpack.BaseHasUuid;
import com.getperka.flatpack.client.dto.ApiDescription;
import com.getperka.flatpack.client.dto.EndpointDescription;
import com.getperka.flatpack.client.dto.ParameterDescription;
import com.getperka.flatpack.ext.EntityDescription;
import com.getperka.flatpack.ext.JsonKind;
import com.getperka.flatpack.ext.Property;
import com.getperka.flatpack.ext.Type;
import com.getperka.flatpack.util.FlatPackCollections;
public class JavaScriptDialect implements Dialect {
@Flag(tag = "packageName",
help = "The name of the package that generated sources should belong to",
defaultValue = "com.getperka.client")
static String packageName;
private static final Logger logger = LoggerFactory.getLogger(JavaScriptDialect.class);
private static final Map<String, String> validationMap = new HashMap<String, String>();
static {
validationMap.put("javax.validation.Valid",
"com.getperka.flatpack.validation.Valid");
validationMap.put("javax.validation.constraints.AssertFalse",
"com.getperka.flatpack.validation.AssertFalse");
validationMap.put("javax.validation.constraints.AssertTrue",
"com.getperka.flatpack.validation.AssertTrue");
validationMap.put("javax.validation.constraints.DecimalMax",
"com.getperka.flatpack.validation.Max");
validationMap.put("javax.validation.constraints.DecimalMin",
"com.getperka.flatpack.validation.Min");
validationMap.put("javax.validation.constraints.Future",
"com.getperka.flatpack.validation.Future");
validationMap.put("javax.validation.constraints.Min",
"com.getperka.flatpack.validation.Min");
validationMap.put("javax.validation.constraints.Max",
"com.getperka.flatpack.validation.Max");
validationMap.put("javax.validation.constraints.NotNull",
"com.getperka.flatpack.validation.NotNull");
validationMap.put("javax.validation.constraints.Null",
"com.getperka.flatpack.validation.Null");
validationMap.put("javax.validation.constraints.Past",
"com.getperka.flatpack.validation.Past");
validationMap.put("javax.validation.constraints.Size",
"com.getperka.flatpack.validation.Size");
// validationMap.put("javax.validation.constraints.Digits",
// "com.getperka.flatpack.validation.");
// validationMap.put("javax.validation.constraints.Pattern",
// "com.getperka.flatpack.validation.");
}
private static String upcase(String s) {
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
private EntityDescription baseHasUuid;
private List<String> entityRequires;
@Override
public void generate(ApiDescription api, File outputDir) throws IOException {
if (!outputDir.isDirectory() && !outputDir.mkdirs()) {
logger.error("Could not create output directory {}", outputDir.getPath());
return;
}
// first collect just our model entities
Set<String> requires = new HashSet<String>();
Map<String, EntityDescription> allEntities =
FlatPackCollections.mapForIteration();
for (EntityDescription entity : api.getEntities()) {
addEntity(allEntities, requires, entity);
}
entityRequires = new ArrayList<String>(requires);
Collections.sort(entityRequires);
// Render entities
STGroup group = loadGroup();
ST entityST = null;
for (EntityDescription entity : allEntities.values()) {
entityST = group.getInstanceOf("entity").add("entity", entity);
render(entityST, outputDir, camelCaseToUnderscore(entity.getTypeName()) + ".js");
}
// render api stubs
ST apiST = group.getInstanceOf("api").add("api", api);
render(apiST, outputDir, "generated_base_api.js");
}
@Override
public String getDialectName() {
return "js";
}
/**
* Adds an entity and its supertypes to a map. The properties defined by the entity will be pruned
* so that the entity contains only its declared properties.
*
* @param allEntities an accumulator map of entity payload names to descriptions
* @param entity the entity to add
*/
protected void addEntity(Map<String, EntityDescription> allEntities,
Set<String> requires, EntityDescription entity) {
if (entity == null) {
return;
}
String typeName = entity.getTypeName();
if (allEntities.containsKey(typeName)) {
// Already processed
return;
} else if ("baseHasUuid".equals(typeName)) {
// Ensure that the "real" implementations are used
baseHasUuid = entity;
return;
} else if ("hasUuid".equals(typeName)) {
// Ensure that the "real" implementations are used
return;
}
allEntities.put(typeName, entity);
for (Iterator<Property> it = entity.getProperties().iterator(); it.hasNext();) {
Property prop = it.next();
if ("uuid".equals(prop.getName())) {
// Crop the UUID property
it.remove();
} else if (!prop.getEnclosingType().equals(entity)) {
// Remove properties not declared in the current type
it.remove();
}
}
requires.add(getPackageName(entity) + "." + upcase(entity.getTypeName()));
// Add the supertype
addEntity(allEntities, requires, entity.getSupertype());
}
private String camelCaseToUnderscore(String s) {
return s.replaceAll(
String.format("%s|%s|%s", "(?<=[A-Z])(?=[A-Z][a-z])",
"(?<=[^A-Z])(?=[A-Z])", "(?<=[A-Za-z])(?=[^A-Za-z])"), "_")
.toLowerCase();
}
private String collectionNameForProperty(Property p) {
return upcase(p.getType().getListElement().getName()) + "Collection";
}
private String getBuilderReturnType(EndpointDescription end) {
// Convert a path like /api/2/foo/bar/{}/baz to FooBarBazMethod
String path = end.getPath();
String[] parts = path.split(Pattern.quote("/"));
StringBuilder sb = new StringBuilder();
sb.append(upcase(end.getMethod().toLowerCase()));
for (int i = 3, j = parts.length; i < j; i++) {
try {
String part = parts[i];
if (part.length() == 0) {
continue;
}
StringBuilder decodedPart = new StringBuilder(URLDecoder
.decode(part, "UTF8"));
// Trim characters that aren't legal
for (int k = decodedPart.length() - 1; k >= 0; k--) {
if (!Character.isJavaIdentifierPart(decodedPart.charAt(k))) {
decodedPart.deleteCharAt(k);
}
}
// Append the new name part, using camel-cased names
String newPart = decodedPart.toString();
if (sb.length() > 0) {
newPart = upcase(newPart);
}
sb.append(newPart);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
sb.append("Request");
return packageName + "." + upcase(sb.toString());
}
private String getNameForType(Type type) {
String name = type.getName();
name = name == null || name.trim().length() == 0 ? type.getJsonKind().name()
.toLowerCase() : name;
return name;
}
private String getPackageName(EntityDescription entity) {
return entity.getTypeName().equals("baseHasUuid") ?
"com.getperka.flatpack.core" : packageName;
}
private List<Property> getSortedCollectionProperties(EntityDescription entity) {
List<Property> properties = new ArrayList<Property>();
for (Property p : entity.getProperties()) {
if (p.getType().getListElement() != null &&
p.getType().getListElement().getName() != null) {
properties.add(p);
}
}
List<Property> sortedProperties = new ArrayList<Property>();
sortedProperties.addAll(properties);
Collections.sort(sortedProperties, new Comparator<Property>() {
@Override
public int compare(Property p1, Property p2) {
return p1.getName().compareTo(p2.getName());
}
});
return sortedProperties;
}
private String getValidationParameters(Annotation annotation) {
String params = "";
if (annotation instanceof Min) {
params += ((Min) annotation).value();
}
if (annotation instanceof Max) {
params += ((Max) annotation).value();
}
if (annotation instanceof DecimalMin) {
params += ((DecimalMax) annotation).value();
}
if (annotation instanceof DecimalMax) {
params += ((DecimalMax) annotation).value();
}
if (annotation instanceof Size) {
Size size = (Size) annotation;
params += size.min() + ", " + size.max();
}
return params;
}
private boolean hasCustomRequestBuilderClass(EndpointDescription end) {
return (end.getQueryParameters() != null && !end.getQueryParameters().isEmpty()) ||
(end.getReturnType() != null && end.getReturnType().getUuid() != null);
}
/**
* Converts the given docString to be jsDoc compatible
*
* @param docString
* @return
*/
private String jsDocString(String docString) {
if (docString == null) return docString;
String newDocString = docString;
try {
// replace <entityReference> tags with /link /endlink pairs
Pattern regex = Pattern.compile(
"<entityReference payloadName='([^']*)'>([^<]*)</entityReference>",
Pattern.CASE_INSENSITIVE);
Matcher matcher = regex.matcher(docString);
while (matcher.find()) {
Type type = new Type.Builder().withName(matcher.group(1).trim()).build();
String jsType = jsTypeForType(type);
String link = "{@link " + jsType + "}";
newDocString = newDocString.replaceAll(matcher.group(0), link);
}
// replace #getFoo() method calls to #foo() method calls
regex = Pattern.compile(
"#get([^(]*)" + Pattern.quote("()"),
Pattern.CASE_INSENSITIVE);
matcher = regex.matcher(docString);
while (matcher.find()) {
String newMethod = "#" + matcher.group(1);
newDocString = newDocString.replaceAll(matcher.group(0), newMethod);
}
}
catch (Exception e) {
logger.error("Couldn't doxygenize doc string: " + docString, e);
}
return newDocString;
}
private String jsTypeForType(Type type) {
if (type.getName() != null) {
// if the type is an enum, the type will be a string
if (type.getEnumValues() != null) {
return "String";
}
return packageName + "." + upcase(type.getName());
}
if (type.getTypeHint() != null
&& type.getTypeHint().getValue().equals("org.joda.time.DateTime")) {
return "Date";
}
else if (type.getTypeHint() != null
&& type.getTypeHint().getValue().equals("org.joda.time.LocalDateTime")) {
return "LocalDate";
}
else if (type.getTypeHint() != null
&& (type.getTypeHint().getValue().equals("java.math.BigDecimal")
|| type.getTypeHint().getValue().equals("java.math.BigInteger"))) {
return "Number";
}
String jsType = "nil";
switch (type.getJsonKind()) {
case BOOLEAN:
jsType = "Boolean";
break;
case DOUBLE:
jsType = "Number";
break;
case ANY:
jsType = "Object";
break;
case INTEGER:
jsType = "Number";
break;
case LIST:
if (type.getListElement() != null && (type.getListElement().getEnumValues() != null ||
type.getListElement().getName() == null)) {
jsType = "Array";
} else {
jsType = "Backbone.Collection";
}
break;
case MAP:
jsType = "Object";
break;
case NULL:
jsType = "null";
break;
case STRING:
jsType = "String";
break;
default:
break;
}
return jsType;
}
/**
* Load {@code js.stg} from the classpath and configure a number of model adaptors to add virtual
* properties to the objects being rendered.
*/
private STGroup loadGroup() {
STGroup group = new STGroupFile(getClass().getResource("js.stg"), "UTF8", '<', '>');
group.registerRenderer(EntityDescription.class, new AttributeRenderer() {
@Override
public String toString(Object o, String formatString, Locale locale) {
EntityDescription entity = (EntityDescription) o;
if (entity.getTypeName().equals("baseHasUuid")) {
return BaseHasUuid.class.getCanonicalName();
}
return entity.getTypeName();
}
});
group.registerModelAdaptor(EntityDescription.class,
new ObjectModelAdaptor() {
@Override
public Object getProperty(Interpreter interp, ST self, Object o,
Object property, String propertyName)
throws STNoSuchPropertyException {
EntityDescription entity = (EntityDescription) o;
if ("docString".equals(propertyName)) {
return jsDocString(entity.getDocString());
}
else if ("canonicalName".equals(propertyName)) {
return getPackageName(entity) + "." + upcase(entity.getTypeName());
}
else if ("supertype".equals(propertyName)) {
EntityDescription supertype = entity.getSupertype();
if (supertype == null) {
supertype = baseHasUuid;
}
return supertype;
}
else if ("properties".equals(propertyName)) {
Map<String, Property> propertyMap = new HashMap<String, Property>();
for (Property p : entity.getProperties()) {
propertyMap.put(p.getName(), p);
}
List<Property> sortedProperties = new ArrayList<Property>();
sortedProperties.addAll(propertyMap.values());
Collections.sort(sortedProperties, new Comparator<Property>() {
@Override
public int compare(Property p1, Property p2) {
return p1.getName().compareTo(p2.getName());
}
});
return sortedProperties;
}
else if ("collectionProperties".equals(propertyName)) {
return getSortedCollectionProperties(entity);
}
else if ("uniqueTypeCollectionListProperties".equals(propertyName)) {
List<Property> props = getSortedCollectionProperties(entity);
Iterator<Property> iter = props.iterator();
Set<String> seen = new HashSet<String>();
while (iter.hasNext()) {
Property prop = iter.next();
String name = collectionNameForProperty(prop);
if (!seen.contains(name)) {
seen.add(name);
}
else {
iter.remove();
}
}
return props;
}
else if ("validations".equals(propertyName)) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
for (Property p : entity.getProperties()) {
List<String> validations = new ArrayList<String>();
List<Annotation> docAnnotations = p.getDocAnnotations();
if (docAnnotations != null) {
for (Annotation a : docAnnotations) {
String name = a.annotationType().getName();
String validation = validationMap.get(name);
if (validation != null) {
validation = "new " + validation + "(";
validation += getValidationParameters(a);
validation += ")";
validations.add(validation);
}
}
}
if (!validations.isEmpty()) {
map.put(p.getName(), validations);
}
}
return map;
}
else if ("validationRequires".equals(propertyName)) {
Set<String> requires = new HashSet<String>();
for (Property p : entity.getProperties()) {
List<Annotation> docAnnotations = p.getDocAnnotations();
if (docAnnotations != null) {
for (Annotation a : docAnnotations) {
String name = a.annotationType().getName();
String require = validationMap.get(name);
if (require != null) {
requires.add(require);
}
}
}
}
return requires;
}
return super.getProperty(interp, self, o, property, propertyName);
}
});
group.registerModelAdaptor(Property.class, new ObjectModelAdaptor() {
@Override
public Object getProperty(Interpreter interp, ST self, Object o,
Object property, String propertyName)
throws STNoSuchPropertyException {
Property p = (Property) o;
if ("docString".equals(propertyName)) {
String docString = jsDocString(p.getDocString());
List<String> enumValues = p.getType().getEnumValues();
if (enumValues == null && p.getType().getListElement() != null &&
p.getType().getListElement().getEnumValues() != null) {
enumValues = p.getType().getListElement().getEnumValues();
}
if (enumValues != null) {
docString = docString == null ? "" : docString;
docString += "\n\nPossible values: ";
for (int i = 0; i < enumValues.size(); i++) {
docString += enumValues.get(i);
if (i < enumValues.size() - 1) docString += ", ";
}
}
return docString;
}
else if ("jsType".equals(propertyName)) {
return jsTypeForType(p.getType());
}
else if ("listElementEnum".equals(propertyName)) {
if (p.getType() != null && p.getType().getListElement() != null &&
p.getType().getListElement().getEnumValues() != null) {
String enumVals = "";
int idx = 0;
for (String val : p.getType().getListElement().getEnumValues()) {
if (idx != 0) {
enumVals += ", ";
}
enumVals += val;
idx++;
}
return enumVals;
}
}
else if ("listElementKind".equals(propertyName)) {
if (p.getType().getListElement() != null &&
p.getType().getListElement().getName() != null) {
return (packageName + "." + upcase(p.getType().getListElement().toString()));
}
}
else if ("collectionName".equals(propertyName)) {
return collectionNameForProperty(p);
}
else if ("canonicalListElementKind".equals(propertyName)) {
if (p.getType().getListElement() != null) {
String collectionModelType = jsTypeForType(p.getType().getListElement());
Property implied = p.getImpliedProperty();
if (implied != null) {
return "function(attrs, options) {\n" +
" return new " + collectionModelType + "(\n" +
" _(attrs).extend({ " + implied.getName()
+ " : self }), options);\n" +
" }";
}
else {
return collectionModelType;
}
}
}
else if ("defaultValue".equals(propertyName)) {
String defaultVal = "undefined";
if (p.isEmbedded()) {
defaultVal = "new " + jsTypeForType(p.getType()) + "()";
}
if (p.getType().getJsonKind().equals(JsonKind.LIST) &&
jsTypeForType(p.getType().getListElement()).equals("String")) {
defaultVal = "[]";
jsTypeForType(p.getType());
}
else if (p.getType().getJsonKind().equals(JsonKind.LIST) &&
p.getType().getListElement().getName() != null) {
defaultVal = "new " + collectionNameForProperty(p) + "()";
}
else if (p.getType().getJsonKind().equals(JsonKind.MAP)) {
return "{}";
}
return defaultVal;
}
return super.getProperty(interp, self, o, property, propertyName);
}
});
group.registerModelAdaptor(String.class, new ObjectModelAdaptor() {
@Override
public Object getProperty(Interpreter interp, ST self, Object o,
Object property, String propertyName)
throws STNoSuchPropertyException {
final String string = (String) o;
if ("chunks".equals(propertyName)) {
/*
* Split a string into individual chunks that can be reflowed. This implementation is
* pretty simplistic, but helps make the generated documentation at least somewhat more
* readable.
*/
return new Iterator<CharSequence>() {
int index;
int length = string.length();
CharSequence next;
{
advance();
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public CharSequence next() {
CharSequence toReturn = next;
advance();
return toReturn;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void advance() {
int start = advance(false);
int end = advance(true);
if (start == end) {
next = null;
} else {
next = string.subSequence(start, end);
}
}
/**
* Advance to next non-whitespace character.
*/
private int advance(boolean whitespace) {
while (index < length
&& (whitespace ^ Character.isWhitespace(string.charAt(index)))) {
index++;
}
return index;
}
};
}
return super.getProperty(interp, self, o, property, propertyName);
}
});
group.registerModelAdaptor(ApiDescription.class,
new ObjectModelAdaptor() {
@Override
public Object getProperty(Interpreter interp, ST self, Object o,
Object property, String propertyName)
throws STNoSuchPropertyException {
ApiDescription apiDescription = (ApiDescription) o;
if ("endpoints".equals(propertyName)) {
Set<EndpointDescription> uniqueEndpoints =
new HashSet<EndpointDescription>(apiDescription.getEndpoints());
List<EndpointDescription> sortedEndpoints = new ArrayList<EndpointDescription>(
uniqueEndpoints);
Collections.sort(sortedEndpoints, new Comparator<EndpointDescription>() {
@Override
public int compare(EndpointDescription e1, EndpointDescription e2) {
return e1.getPath().compareTo(e2.getPath());
}
});
Iterator<EndpointDescription> iter = sortedEndpoints.iterator();
while (iter.hasNext()) {
EndpointDescription ed = iter.next();
if (ed.getPath() != null && ed.getPath().contains("{path:.*}")) {
iter.remove();
}
}
return sortedEndpoints;
}
else if ("flatpackEndpoints".equals(propertyName)) {
List<EndpointDescription> sortedEndpoints = new ArrayList<EndpointDescription>(
apiDescription.getEndpoints());
Collections.sort(sortedEndpoints, new Comparator<EndpointDescription>() {
@Override
public int compare(EndpointDescription e1, EndpointDescription e2) {
return e1.getPath().compareTo(e2.getPath());
}
});
Iterator<EndpointDescription> iter = sortedEndpoints.iterator();
while (iter.hasNext()) {
if (!hasCustomRequestBuilderClass(iter.next())) {
iter.remove();
}
}
return sortedEndpoints;
}
else if ("requireNames".equals(propertyName)) {
return entityRequires;
}
return super.getProperty(interp, self, o, property, propertyName);
}
});
group.registerModelAdaptor(EndpointDescription.class,
new ObjectModelAdaptor() {
@Override
public Object getProperty(Interpreter interp, ST self, Object o,
Object property, String propertyName)
throws STNoSuchPropertyException {
EndpointDescription end = (EndpointDescription) o;
if ("docString".equals(propertyName)) {
return jsDocString(end.getDocString());
}
if ("methodName".equals(propertyName)) {
String path = end.getPath();
String[] parts = path.split(Pattern.quote("/"));
StringBuilder sb = new StringBuilder();
sb.append(end.getMethod().toLowerCase());
for (int i = 3, j = parts.length; i < j; i++) {
String part = parts[i];
if (part.length() == 0) continue;
if (!part.startsWith("{") && !part.endsWith("}")) {
if (part.contains(".")) {
String[] dotPart = part.split(Pattern.quote("."));
for (String dot : dotPart) {
sb.append(upcase(dot));
}
}
else {
sb.append(upcase(part));
}
}
else {
sb.append(upcase(part.substring(1, part.length() - 1)));
}
}
return sb.toString();
}
else if ("methodParameterList".equals(propertyName)) {
String path = end.getPath();
String[] parts = path.split(Pattern.quote("/"));
StringBuilder sb = new StringBuilder();
int paramCount = 0;
for (int i = 3, j = parts.length; i < j; i++) {
String part = parts[i];
if (part.length() == 0) continue;
if (part.startsWith("{") && part.endsWith("}")) {
String name = part.substring(1, part.length() - 1);
sb.append(name);
paramCount++;
if (end.getPathParameters() != null
&& paramCount < end.getPathParameters().size()) {
sb.append(", ");
}
}
}
if (end.getEntity() != null) {
if (paramCount > 0) {
sb.append(", ");
}
sb.append(getNameForType(end.getEntity()));
}
return sb.toString();
}
else if ("entityName".equals(propertyName)) {
return getNameForType(end.getEntity());
}
else if ("requestBuilderClassName".equals(propertyName)) {
if (hasCustomRequestBuilderClass(end)) {
return getBuilderReturnType(end);
}
else {
return "com.getperka.flatpack.client.JsonRequest";
}
}
else if ("requestBuilderBlockName".equals(propertyName)) {
return getBuilderReturnType(end) + "Block";
}
else if ("pathDecoded".equals(propertyName)) {
// URL-decode the path in the endpoint description
try {
String decoded = URLDecoder.decode(end.getPath(), "UTF8");
return decoded;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
return super.getProperty(interp, self, o, property, propertyName);
}
});
group.registerModelAdaptor(ParameterDescription.class,
new ObjectModelAdaptor() {
@Override
public Object getProperty(Interpreter interp, ST self, Object o,
Object property, String propertyName)
throws STNoSuchPropertyException {
ParameterDescription param = (ParameterDescription) o;
if ("requireName".equals(propertyName)) {
return upcase(param.getName());
}
else if ("docString".equals(propertyName)) {
return jsDocString(param.getDocString());
}
return super.getProperty(interp, self, o, property, propertyName);
}
});
Map<String, Object> namesMap = new HashMap<String, Object>();
namesMap.put("packageName", packageName);
group.defineDictionary("names", namesMap);
return group;
}
private void render(ST enumST, File packageDir, String fileName)
throws IOException {
if (!packageDir.isDirectory() && !packageDir.mkdirs()) {
logger
.error("Could not create output directory {}", packageDir.getPath());
return;
}
Writer fileWriter = new OutputStreamWriter(new FileOutputStream(new File(
packageDir, fileName)), "UTF8");
AutoIndentWriter writer = new AutoIndentWriter(fileWriter);
writer.setLineWidth(80);
enumST.write(writer);
fileWriter.close();
}
}